2020-11-7 도커 강의정리 멀티컨테이너 배포하기

실제배포하기(복잡한)

  • 리액트, 노드, 데이터베이스 함께 배포해야한다. 멀티컨테이너

스크린샷 2020-10-17 오전 1.09.10

스크린샷 2020-10-17 오전 1.09.27

Nginx의 Proxy를 이용한 설계

  • 운영환경을 위한 FrontDocker

    • FROM node:alpine as builder
      WORKDIR /app
      COPY ./package.json ./
      RUN npm install
      COPY . .
      RUN npm run build
      
      
      FROM nginx
      EXPOSE 3000
      COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
      COPY --from=builder /app/build  /usr/share/nginx/html
      
    • 운영환경에서는 개발서버가 NGINX로 대체된다.

    • server {
          listen 3000;
          location / {
              root /usr/share/nginx/html;
              index index.html index.htm;
              try_files $uri  $uri/ /index.html; => 정말중요.
          }
      }
      
      • listen 3000 => nginx가 listen하고 있는 PORT를 명시
      • location / => location /를 할때 어떤일을 할지 명시.
      • root /usr/share/nginx/html => HTML 파일이 위치할 루트 설정. 루트도 변경할 수 있다. 기본은 /usr/share/nginx/html에 있다.
      • index index.html index.htm => 사이트의 index페이지로 할 파일명 작성
      • try_files $uri $uri/ /index.html => React Router를 사용해서 페이지간 이동할때 필요. React가 SPA기 때문에 index.html 하나의 정적파일만 가지고 있다. {URL}/home으로 이동하려고 해도 index.html에 접근해서 라우팅을 해야된다. nginx에서는 이 부분을 할 수 없기때문에 /home로 접속할때 매칭되는 것이 없어서 대안으로 index.html을 제공하고 /home으로 라우팅할 수 있게 임의로 설정하는 부분이다.
      • /etc/nginx/conf.d/default.conf는 컨테이너안에 있는 nginx설정파일의 경로
    • ./nginx/default.conf는 nginx설정 파일을 복사한다.

    • Nginx를 가동하고, 빌더스테이지에서 만들어진 build파일들을 nginx폴더로 이동한다.

    • COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf는 우리가 작성한 nginx설정파일을 /etc/nginx/conf.d/default.conf로 덮어씌우는거다.

데이터베이스 도커구성

  • 개발환경에서는 도커환경을 이용
  • 운영환경에서는 AWS RDS를 이용
    • nginx, front, backend는 ElasticBeansTalk에 있지만 DB에 대해서만 RDS를 이용
FROM mysql:5.7

ADD ./my.cnf /etc/mysql/conf.d/my.cnf

  • mysql컨테이너안으로 ./my.cnf파일을 /etc/mysql/conf.d/my.cnf로 덮어 씌어야한다.
// my.cnf
[mysqld]
character-set-server=utf8

[mysql]
default-character-set=utf8

[client]
default-character-set=utf8

  • 한글이 깨질 수 있어서 my.cnf를 추가해야한다.
// sql/initialize.sql
DROP DATABASE IF EXISTS myapp;

CREATE DATABASE myapp;
USE myapp;

CREATE TABLE lists (
    id INTEGER AUTO_INCREMENT,
    value TEXT,
    PRIMARY KEY (id)
);

Nginx 구성

  • Proxy에서 요청 처리를 하는 Nginx 컨테이너를 구성한다.

  • 운영환경과 개발환경의 차이가 없어서 Dockerfile을 1개만 만든다.

  • 이 프로젝트에서 Nginx는 Proxy를 처리하는 부분과 Static파일을 처리하는 부분으로 구성된다.

  • request가 /api로 시작되면 서버로. 그 외에는 프론트로 요청을 보낸다.

  • upstream frontend {
        server frontend:3000;
    }
    upstream backend {
        server backend:5000;
    }
    server {
        listen 80;
        location / {
            proxy_pass http://frontend;
        }
        location /api {
            proxy_pass http://backend;
        }
        location /sockjs-node {
            proxy_pass http://frontend;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "Upgrade";
        }
    }
    
  • upstream frontend => frontend가 3000번 포트에서 돌아가고 있다고 명시

  • upstream backend => backend가 5000번 포트에서 돌아가고 있다고 명시

  • frontend, backend는 docker-compose에서 서비스 아래 컨테이너이름을 명시해야한다.

  • listen 80 => 서버 포트 80번을 열어둔다.

  • location에는 우선순위가 있다.

    • location /는 우선순위가 가장 낮다. 우선은 /api로 시작되는걸 찾고 없으면 /이 시작되고 frontend로 요청을 보낸다.
    • /api로 시작되는건 backend로 요청을 보낸다.
  • /sockjs-node가 없으면 에러가 발생한다. 리액트 개발환경에서 필요.

도커 컴포즈 파일 작성하기

version: "3"

services:
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile.dev
    ports:
    - 3000:3000
    volumes:
    - /app/node_modules
    - ./frontend:/app
    stdin_open: true
  nginx:
    restart: always
    build:
      dockerfile: Dockerfile
      context: ./nginx
    ports:
      - "3000:80"
  backend:
    build:
      dockerfile: Dockerfile.dev
      context: ./backend
    container_name: app_backend
    volumes:
    - /app/node_modules
    - ./backend:/app
  mysql:
    build: ./mysql
    restart: unless-stopped
    container_name: app_mysql
    ports:
    - "3306:3306"
    volumes:
    - ./mysql/mysql_data:/var/lib/mysql
    - ./mysql/sqls/:/docker-entrypoint-initdb.d/
    environment:
      MYSQL_ROOT_PASSWORD: 12345
      MYSQL_DATABASE: test
  • nginx restart: nginx재시작정책(no, always, on-failure, unless-stopped)
    • no : 재시작 안한다.
    • always : 항상 재시작
    • on-failure 에러코드와 함께 컨테이너가 멈출때만 재시작
    • unless-stopped : 개발자가 임의로 멈출때 빼고 항상 재시작

Volume을 이용하여 데이터베이스 데이터 유지하기

  • 원래 컨테이너를 지우면 컨테이너에 저장된 데이터들이 지워진다. 데이터가 컨테이너 안에 저장되기 때문
  • 영속성이 필요한 데이터들을 위해 volume을 이용한다.
  • 호스트 파일시스템을 볼륨으로 활용하여 도커 컨테이너가 지워지더라도 데이터가 유지되도록 한다.
  • 컨테이너에서 변화된 데이터가 컨테이너에 저장되는 것이 아니라 호스트 파일시스템에 저장

Dockerrun.aws.json

  • Docker container세트를 EB 고유의 JSON파일이다. 멀티 컨테이너 환경에서 사용한다.

  • 이 파일이 있어야 AWS ElasticBeansTalk에서 App을 작동시킬 수 있다.
  • Dockerfile이 하나인 경우, EB가 알아서 빌드된 이미지를 실행시켜서 어플리케이션을 실행했다.
  • Node, React, Nginx 등 여러개의 Dockerfile을 돌리는 경우 EB가 어떤 파일을 먼저 실행하고 행동을 해야되는지 알 수 없기때문에 Dockerrun.aws.json에 명시를 해야한다.

스크린샷 2020-10-18 오후 10.52.45

  • Task Definition과 Container Definition을 해야한다.
  • Dockerrun.aws.json에 Container Definition을 명시
  • Container Definition을 명시하면 Docker Daemon으로 전달되어 Docker daemon이 실행한다.
  • Task Definition에서 지정할 수 있는것
    • 작업의 각 컨테이너에 사용할 도커 이미지
    • 컨테이너에서 사용할 CPU, 메모리양
    • 호스팅되는 인스파결정
    • 도커 네트워킹 모드
    • 로깅구성
    • 컨테이너가 종료, 실패되도 작업 계속할지
    • 컨테이너 시작 시 컨테이너가 실행할 명령
    • 컨테이너가 사용할 데이터 볼륨
    • IAM역할
{
  "AWSEBDockerrunVersion": 2,
  "containerDefinitions": [
    {
      "name": "frontend",
      "image": "jimmy/docker-frontend",
      "hostname": "frontend",
      "essential": false,
      "memory": 128
    },
    {
      "name": "backend",
      "image": "jimmy/docker-backend",
      "hostname": "backend",
      "essential": false,
      "memory": 128
    },
    {
      "name": "nginx",
      "image": "jimmy/docker-nginx",
      "hostname": "nginx",
      "essential": true,
      "portMappings": [
        {
          "hostPort": 80,
          "containerPort": 80
        }
      ],
      "links": ["frontend", "backend"],
      "memory": 128
    }
  ]
}
  • containerDefinitions안에 컨테이너들을 정의
  • name : 컨테이너 이름
  • image : Docker 레포지토리의 도커 이미지 => 도커허브에 업로드된 이미지
  • hostname : 호스트이름, 이 이름을 활용하여 도커컴포즈를 이용하여 다른 컨테이너에서 접근가능
  • essential : 컨테이너가 실패하고 작업을 중지해야되면 true
  • memory : 컨테이너 인스턴스의 메모리
  • portMapping : 컨테이너에 있는 네트워크 지점을 호스트에 있는 지점으로 매핑
  • links : 연결할 컨테이너 목록. nginx가 frontend와 backend 컨테이너를 연결한다.

다중컨테이너 ElasticBeansTalk 환경구성

  • ElasticBeansTalk 환경구성을 기존 Single Continaer에서 Multi Container환경으로 재구성해야한다.

VPC

  • RDS의 MySQL와 애플리케이션을 연결시키기 위해서는 VPC와 Security Group이 필요하다.
  • EB 인스턴스와 RDS 함께 사용을 많이하는데 기본적으로 연결되어있지 않아서 통신할 수 없다. 통신할 수 있도록 연결해줘야한다.
  • VPC(Virtual Private Cloud)로 AWS에서 논리적으로 격리된 공간을 프로비저닝하여 사용자가 정의한 가상네트워크에서 AWS 리소스를 실행할 수 있다. EC2, EB, RDS 등 내가 생성한 인스턴스를 나만이 접근할 수 있도록 네트워크를 논리적으로 구성하게된다. 다른아이디에서는 접속, 조회가 안된다.
  • EB, RDS를 생성하면 기본VPC(default VPC)가 할당된다. 할당될때는 리전별로 다르게 할당
  • AP-Northeast-2에 할당된 기본 VPC에 EB인스턴스와 RDS가 함께 존재하지만 통신을 할 수 없다. 통신하기 위해서는 따로 설정.

Security Group

스크린샷 2020-10-18 오후 11.22.42

  • 인바운드 : 외부에서 EC2, EB인스턴스로 요청을 보내는 트랙픽. HTTP, HTTPS, SSH 등이 있다.
  • 아웃바운드 : EC2, EB 등에서 나가는 트래픽. 파일을 다운로드 하거나 인바운드 트래픽을 처리하여 응답하는 경우
  • Security Group이 인바인드와 아웃바운드를 통제해서 트래픽을 열어줄수도 닫을수도 있다.
  • RDS를 위해 Security Group에서 inbound 3306을 허용해야한다.
  • Security Group, VPC를 설정해도 EB와 MySQL이 소통할때 환경변수 부분을 인식하지 못한다. EB에서 환경변수를 설정해야한다.
  • EB의 환경-구성-소프트웨어편집에서 환경변수를 설정해야한다. MYSQL_ROOT_PASSWORD, MYSQL_HOST 등등

배포코드

deploy:
  provider: elasticbeanstalk
  region: "ap-northeast-2"
  app: "docker-multicontainer"
  env: "DockerMulticontainer-env"
  bucket_name: "elasticbeanstalk-ap-northeast-2-194829314461"
  bucket_path: "docker-react-app"
  on:
    branch: master
  access_key_id: $AWS_ACCESS_KEY
  secret_key_key: $AWS_SECRET_ACCESS_KEY

Reference

  • https://www.inflearn.com/course/%EB%94%B0%EB%9D%BC%ED%95%98%EB%A9%B0-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%8F%84%EC%BB%A4-ci/lecture/52087?tab=curriculum&speed=1.25
Written on November 7, 2020