도커 - 복잡한 테스트 배포 부분

1. 전체적 구성도


그림1

  • 짤린 부분은 이미지 생성 후 Docker Hub으로 전달
  • AWS RDB 사용
    • 운영 환경에서는 MYSQL이 도커에서 돌아가는게 아니라 AWS에서 돌아가게 한다.
    • 앞서 docker compose에서 작성했던 mysql 코드를 전부 주석 처리

2. Travis CI Steps


그림2 그림3 그림4

# .travis.yml

language: generic

sudo: required

services:
  - docker

before_install:
  - docker build -t backtony/react-test-app -f ./frontend/Dockerfile.dev ./frontend

script:
  - docker run -e CI=true backtony/react-test-app npm test

after_success:
  - docker build -t backtony/docker-frontend ./frontend
  - docker build -t backtony/docker-backend ./backend
  - docker build -t backtony/docker-nginx ./nginx

  - echo "$DOCKER_HUB_PASSWORD" | docker login -u "$DOCKER_HUB_ID" --password-stdin

  - docker push backtony/docker-frontend
  - docker push backtony/docker-backend
  - docker push backtony/docker-nginx
  • 테스트하기 전에 Dockerfile.dev 설정으로 이미지 빌드하고 테스트 진행
  • 성공하면 운영버전으로 이미지 빌드
  • Travis CI에 Settings에서 변수로 docker hub 아이디, 비밀번호 세팅
  • echo를 이용해서 로그인
  • docker hub에 이미지 푸시


3. Dockerrun.aws.json


그림5

  • 노드, Mysql, Nginx 등을 위한 Dockerfile이 여러 개 있는 경우 Elastic Beanstalk이 어떤 파일을 먼저 실행하고 어떤 행동을 취해야하는지 자동으로 처리할 수 없기 때문에 임의로 설정이 필요한데 이때 사용하는 파일
  • Docker 컨테이너 세트를 Elastic Beanstalk 애플리케이션으로 배포하는 방법을 설명하는 Elastic Beanstalk 고유의 JSON 파일
  • Elastic Beanstalk이 컨테이너를 어떻게 실행해야 하는지 모르기 때문에 Task에 어떻게 컨테이너를 실행해야 하는지 정의하는 것은 Task Definition(작업 정의)라고 하고, 작업 정의를 등록할 때는 Container Definition을 명시해야 하는데 이 Container Definition을 Dockerrun.aws.json에 넣는다. 이는 도커 데몬으로 전달된다.
// Dockerrun.aws.json
{
    "AWSEBDockerrunVersion": 2,
    "containerDefinitions": [
        {
            "name": "frontend",
            "image": "backtony/docker-frontend",
            "hostname": "frontend",
            "essential": false,
            "memory": 128
        },
        {
            "name": "backend",
            "image": "backtony/docker-backend",
            "hostname": "backend",
            "essential": false,
            "memory": 128
        },
        {
            "name": "nginx",
            "image": "backtony/docker-nginx",
            "hostname": "nginx",
            "essential": true,
            "portMappings": [
                {
                    "hostPort": 80,
                    "containerPort": 80
                }
            ],
            "links": ["frontend", "backend"],
            "memory": 128
        }
    ]
}
  • AWSEBDockerrunVersion : dockerrun 버전
  • containerDefinitions : 컨테이너 정의 시작
    • name : 컨테이너 이름
    • image : Docker hub에 올린 이미지 이름
    • hostname : 호스트이름으로 다른 컨테이너에서 해당 컨테이너로 접근할 때 사용하는 이름
    • essential : 값이 true이면 해당 컨테이너가 죽으면 다른 작업도 모두 중지, 작업에 있어서 필수적인 부분에 true
    • memory : 한 컨테이너에서 얼만큼의 메모리를 사용할지 명시
    • portMappings : 포트 매핑
    • links : 연결할 컨테이너의 목록, 연결된 컨테이너는 서로 검색하고 안전하게 통신 가능


cf) Task Definition(작업 정의)에서 지정할 수 있는 것들

  • 작업의 각 컨테이너에 사용할 도커 이미지
  • 각 작업 또는 작업 내 각 컨테이너에서 사용할 CPU 및 메모리의 양
  • 사용할 시작 유형으로서 해당 작업이 호스팅되는 인프라 결정
  • 작업의 컨테이너에 사용할 도커 네트워킹 모드
  • 작업에 사용할 로깅 구성
  • 컨테이너가 종료 또는 실패하더라도 작업이 계속 실행될지 여부
  • 컨테이너 시작 시 컨테이너가 실행할 명령
  • 작업의 컨테이너에서 사용할 데이터 볼륨
  • 작업에서 사용해야 하는 IAM 역할


4. VPC와 Security Group


그림6

  • EB(elastic beanstalk) 인스턴스에서 많은 컨테이너들이 싫행되고 이것과 RDS를 연결시켜줘야 하는데 기본적으로 설정되어있지 않아서 통신할 수 있게 연결시키기 위해 필요한 것이 VPC와 Security Group


VPC

  • Amazon Virtual Private Cloud
  • AWS 클라우드에서 논리적으로 격리된 공간을 제공하여 고객이 정의하는 가상 네트워크에서 AWS 리소스를 시작할 수 있도록 제공하는 서비스
  • 간단하게 설명하면, 내가 만든 EC2 인스턴스, EB 인스턴스, RDS 데이터 베이스에는 나의 아이디에서만 접근이 가능하게 논리적으로 격리된 네트워크에서 생성되게 해준다. 따라서 다른 아이디로는 접근, 보기가 불가능
  • EB 인스턴스나 RDS를 생성하면 자동적으로 기본 VPC가 할당되는데 지역별로 다르게 할당


그림7

같은 VPC안에 있지만 기본적으로 통신할 수 없게 되어 있어서 서로 통신이 불가능한 상태이다.


Security Group

그림8

  • 보안그룹으로 방화벽이라고 부름
  • Inbound : 외부에서 EC2 인스턴스나 EB 인스턴스로 요청을 보내는 트래픽
    • HTTP, HTTPS, SSH 등등
  • Outbound : EC2인스턴스나 EB인스턴스등에서 외부로 나가는 트래픽
    • 파일을 다운로드 하거나 inbound로 들어온 트래픽을 처리하여 응답하는 경우도 포함
  • 결론적으로 Security Group이 inbound와 outbound를 통제해서 트래픽을 열수도 닫을 수도 있음


연결시키기

그림9

  • 같은 VPC 안에 있는 AWS 서비스 간에는 트래픽을 모두 허용할 수 있게 Security Group을 허용
    • 간단하게 말하면, 같은 VPC 안에 있는 경우 원래는 방화벽 때문에 소통이 안됬지만 방화벽 없애서 자유롭게 소통이 가능하도록 한게 설정하면 된다는 뜻


5. RDS 관련 설정


# docker-compose.yml
version: "3"
services:
  frontend:
    build:
      dockerfile: Dockerfile.dev
      context: ./frontend
    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
    environment: 
      MYSQL_HOST: mysql
      MYSQL_USER: root 
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: myapp
      MYSQL_PORT: 3306     
  • docker compose에서 기존에 mysql로 따로 서비스로 작성했던 내용을 지우고 백엔드 부분에 environment로 작성
  • backend폴더 DB연결 코드에서는 연결시키는 부분에서 이제부터 process.env.MYSQL_HOST 와 같은 변수로 사용이 가능
  • RDS는 AWS에서 직접 생성 -> 강의 참고
    • RDS 생성하고 Security Group 생성한뒤 Security Group 을 통해 inbound와 outboud에 RDS 포트(mysql 3306)포트를 열어준다.
    • elasticbeanstalk과 RDS 둘다 설정에서 만든 Security group을 추가해줌으로 인해 같은 VPC에서 오는 트래픽은 모두 허용하게 되어 서로 소통이 가능해 짐
  • elasticbeanstalk에는 컨테이너가 많은데 MYSQL과 소통할 때 mysql의 환경변수 부분을 인식하지 못하므로 elasticbeanstalk에 mysql 관련 환경변수 설정 추가(위에 작성한 environment 내용을 웹사이트에서 설정)


6. 배포


# .travic.yml 

language: generic

sudo: required

services:
  - docker

before_install:
  - docker build -t backtony/react-test-app -f ./frontend/Dockerfile.dev ./frontend

script:
  - docker run -e CI=true backtony/react-test-app npm run test

after_success:
  - docker build -t backtony/docker-frontend ./frontend
  - docker build -t backtony/docker-backend ./backend
  - docker build -t backtony/docker-nginx ./nginx

  - echo "$DOCKER_HUB_PASSWORD" | docker login -u "$DOCKER_HUB_ID" --password-stdin

  - docker push backtony/docker-frontend
  - docker push backtony/docker-backend
  - docker push backtony/docker-nginx

deploy:
  provider: elasticbeanstalk
  region: "ap-northeast-2"
  app: "docker-fullstack-app"
  env: "Dockerfullstackapp-env"
  bucket_name: elasticbeanstalk-ap-northeast-2-****
  bucket_path: "docker-fullstack-app"
  on:
    branch: master
  
  access_key_id: $AWS_ACCESS_KEY
  secret_access_key: $AWS_SECRET_ACCESS_KEY
  • deploy 부분을 추가
  • provider : 외부 서비스 표시(s3, elaticbeanstalk, firebase 등등)
  • region : 현재 사용하고 있는 AWS의 서비스가 위치하고 있는 물리적 장소
    • 한국이라면 ap-northeast-2
  • app : 생성된 애플리케이션의 이름
  • env : 추가적으로 설정하지 않으면 app의름을 붙여서 자동으로 생성됨
    • 웹사이트에 적혀있으니 그것 참고해서 작성
  • bucket_name : elasticbeanstalk 을 위한 s3 버켓 이름
    • Travis CI에서 AWS elasticbeanstalk을 통해 소스 배포과정에서 바로 elastic으로 가는게 아니라 travis에서 가지고 있는 파일을 압축해서 먼저 S3로 보내므로 필요함
    • elasticbeanstalk을 생성할 때 S3가 자동적으로 생성 됨, 웹사이트에서 참고하여 작성
  • bucket_path : 애플리케이션 이름과 동일
  • on branch : 어떤 브랜치에 push를 할때 AWS에 배포할 것인지
  • access key : AWS와 Travis CI를 연동하기 위해 필요한 키
    • AWS IAM을 통해서 키를 발급받고 노출되면 안되므로 Travis CI Setting에서 환경변수로 작성하고 $를 통해 가져와서 사용



본 포스팅은 인프런 John Ahn님의 ‘따라하며 배우는 도커와 CI환경’ 강의를 듣고 정리한 내용을 바탕으로 복습을 위해 작성하였습니다. [강의 링크]


© 2021. By Backtony