Docker - Docker Study3

 



1. Docker로 다중 컨테이너 애플리케이션 구축하기

1) 각 역할에 대한 컨테이너를 도커화하는 방법

a. MongoDB 이미지 도커화

  • 현재, 이 노드 API가 연결할 때, 데이터베이스와 통신하기 때문에, 데이터베이스를 포함하는 컨테이너를 가동하여 이것이 작동하려면, 이 포트 27017를 노출해야 한다.


  • 도커 컨테이너에서 로컬 머신으로 가동시켜야 한다. 우리 서비스가 로컬 머신을 통해 서비스에 연결할 수 있다.


  • MongoDB 이미지는 포트번호 27017를 노출시키고 있다.


  • 통신 URL : mongodb://localhost:27017/course-goals


  • 현재는 아직 도커화되지 않는 로컬의 NodeJS에 연결한다.
// 몽고DB를 컨테이너에서 실행시키기
docker run --name mongodb --rm -d -p 27017:27017 mongo



// 이렇게 하고나서 NODEJS 앱을 실행시키기.
node app.js
// 로컬의 몽고DB를 제거했는데도 컨테이너에 있기 때문에 실행된다.




b. NodeJS 이미지 도커화

  • 현재 이 애플리케이션이 컨테이너 내부에 있으므로 동일한 컨테이너 내부의 이 포트에서 그 서비스에 액세스하려고 합니다. 호스트 머신에 접근하는 것이 아니다.


  • 그래서, 여기서 사용해야 하는 특별한 대체 도메인 또는 주소가 있다.
    • host.docker.internal 이다.
    • 결론** : mongodb://host.docker.internal:27017/course-goals


  • 중요** : 또한, 컨테이너를 실행할 때, 로컬호스트 머신에서 사용가능한 포트를 게시해야 한다.
    • 애플리케이션이 컨테이너 내부에서 수신 대기하는 포트(80)이다.
docker build -t goals-node .


docker run --name goals-backend --rm -d -p 80:80 goals-node
// 이렇게하면 연결된다.



c. React SPA를 도커화시키기

  • Dockerfile 설정
FROM node

WORKDIR /app

COPY package.json .

RUN npm install

COPY . .

EXPOSE 3000

CMD [ "npm", "start" ]



  • Docker 명령어** : 프론트인 React를 컨테이너에 사용하기 위해 인터렉티브 모드가 필요하다.
    • React의 컨테이너는 인터렉티브 모드처럼 상호호환할 수 없으면, 트리거가 동작하여 컨테이너가 종료된다.
docker run --name goals-frontend --rm -p 3000:3000 -it goals-react
// 이렇게 인터렉티브를 추가하면 프론트 컨테이너가 연결된다.



2) 효율적인 컨테이너 간 통신을 위한 Docker 네트워크 추가하기**


  • 도커 네트워크를 사용하면, 더 이상 이 포트를 게시할 필요가 없습니다. 컨테이너가 동일한 네트워크에 있으면 서로 통신할 수 있기 때문이다.
  • 로컬 호스트 머신은 포트 번호를 게시하지 않는 경우, 로컬 호스트 주소를 통해 이 컨테이너와 통신할 수 없었습니다. 새 컨테이너가 서로 대화할 수 있으면, 충분하다.


  • 따라서, 포트번호를 게시하는 것보다 ‘–network’를 추가하고 해당 네트워크에서 이 컨테이너를 실행하도록 하자.


a. MongoDB(DataBase)를 도커 네트워크에 연결하는 방법

  • 그 전에 MongoDB 이미지를 build 명령어로 생성해야 한다.
docker run --name mongodb --rm -d --network goals-network mongo



b. NodeJS(Backend)를 도커 네트워크에 연결하는 방법

  • 그 전에 NodeJS 이미지를 build 명령어로 생성해야 한다.


  • NodeJS 코드 내부의 요청 URL 변경 : mongodb://mongodb:27017/course-goals
docker run --name goals-backend --rm -d --network goals-net goals-node



mongodb://mongodb:27017/course-goals



c. React(Frontend)를 도커 네트워크에 연결하는 방법**

  • React 코드 내부의 초기 요청 URL 변경 : http://goals-backend/goals


  • React 이미지 생성 : docker build -t goals-react .


  • 이 컨테이너는 동일한 네트워크의 일부이며, 다른 컨테이너와 통신할 수 있다.
docker run --name goals-frontend --network goals-net --rm -p 3000:3000 -it goals-react


  • 하지만, 이렇게 실행하면, 브라우저에서 에러가 발생한다. 그래서 아래 코드처럼 변경하자!


  • Dockerfile

FROM node

WORKDIR /app

COPY package.json .

RUN npm install

COPY . .

EXPOSE 3000

CMD [ "npm", "start" ]



중요 : 핵심 1


  • FrontEnd의 App.js에서 요청 경로!
    • 브라우저가 이해할 수 있도록 주소를 백엔드 이미지인 ‘goals-backend’가 아니라 아래처럼 ‘localhost’로 변경해줘야 한다.
http://localhost/goals/


  • Backend의 app.js에서 요청 경로!
    • 요청 URL을 이미지명으로 동일하다면, 이미지명으로도 변경가능
mongodb://mongodb:27017/course-goals


  • 이렇게 설정하면, 아래 명령어를 통해 한 번에 NodeJS 컨테이너 내부에서 React 컨테이너를 사용할 수 있다.
    • NodeJS의 내부 컨테이너에서 ‘80’ 포트번호를 기다리는 것을 명령어로 설정하여 백엔드 애플리케이션에 포트 80을 게시하여 연결시켜 준다.


  • 따라서, 프론트엔드 애플케이션은 로컬 호스트에서도 계속 사용할 수 있다. 프론트엔드 애플리케이션이 접근할 수 있기 때문이고 React의 동작 방식 때문이다. React는 브라우저에서 실행되는 Javascript 코드가 있기 때문이다.


  • 첫 번째 노드 앱와 통신해야 하는 두 번째 노드 애플리케이션이 있다면, 컨테이너 이름을 사용할 수 있다.


  • 하지만 이것은 브라우저에서 실행되는 JavaScript 코드이므로 도커가 아니라, 브라우저가 이해할 수 있는 코드가 필요하다.



중요 : 핵심 2
  • 컨테이너에서 실행되는 부분인 개발 서버는 네트워크를 신경 쓰지 않기 때문이다.


  • 그 개발 서버는 노드 API 또는 데이터베이스와 상호 작용하지 않습니다. 그리고 API와 상호작용하는 부분은 도커 환경에서 실행되지 않는다.
// 이제는 프론트엔드에서 네트워크가 필요 없다.
docker run --name goals-frontend --rm -p 3000:3000 -it goals-react




중요 : 핵심 3
  • 컨테이너의 포트 80을 로컬 호스트 머신의 포트 80에 게시하고자 한다. 그것으로 React 애플리케이션이 이것과 통신할 수 있습니다.
docker run --name goals-backend --rm -d -p 80:80 --network goals-net goals-node


  • 앞으로 해야할 것** : 데이터의 지속성, 제한된 액세스, 그리고 실행 중인 컨테이너에 반영되는 실시간 소스 코드 업데이트.



3) 볼륨으로 MongoDB에 데이터 지속성 추가하기

docker run --name mongodb -v data:/data/db --rm -d --network goals-net -e MONGO_INITDB_ROOT_USERNAM=max -e MONGO_INITDB_ROOT_PASSWORD=secret mongo


  • 이번에는 환경 변수도 추가하여 실행한다는 것이다.
    • 그리고 첫 번째로 추가하고자 하는 것은 MONGO_INITDB_ROOT_USERNAME인데 ‘max’로 설정하도록 한다.
    • 그리고 두 번째 환경 변수를 한다. MONGO_INITDB_ROOT_PASSWORD인데 이건 ‘secret’라고 할당한다.


  • 에러 1 : MongoDB 환경변수 설정
    • MongoDB가 이해하는 특수한 포맷을 갖기 때문이다.
    • 그 포맷은 사용자 이름과 비밀번호를 가지며 호스트 주소 앞에 ‘사용자 이름:비밀번호@’ 형식으로 추가한다.
    • 비밀번호 다음엔 ‘@’기호를 추가하는군요. (@, at이므로 호스트 주소의 사용자계정이란 의미)
    • 이 계정설정은 옵션이지만 지금 우리에겐 필요하다.
    • 사용자 이름과 비밀번호가 있으니까요.
    • 그래서 이 경우에는 ‘max:secret@mongodb’가 된다.


  • 에러 2 : 또 다른 백엔드 에러 발생. 공식 MongoDB 문서에서 그 해결책을 찾을 수 있다.
    • 연결 문자열 끝에 이 ‘authSource=admin’ 쿼리 매개변수를 추가해야 한다.


  • Backend의 app.js에서 요청 경로!
mongodb://max:secret@mongodb:27017/course-goals?authSource=admin



4) NodeJS 컨테이너의 볼륨, 바인딩 마운트 및 폴리싱(Polishing)**

a. NodeJS 컨테이너에 볼륨 생성

  • 볼륨을 한 개 만들고 바인딩 마운트(절대 경로 포함)도 1개 만든다.


  • 우리가 ‘app’ 폴더에 바인딩하는 호스트 시스템 폴더에 존재하지 않는 ‘node_modules’ 폴더로 덮어쓰면 안 된다고 알린다.
    • node_modules 폴더가 의존성에 의해서 받아지는데 로컬 것이 이전 버전이라면 업데이트 시, 문제가 되어서 볼륨에 만들어 둔다.
docker run --name goals-backend -v /Users/maximilianschwarzmuller/development/teaching/udemy/docker-complete/backend:/app -v logs:/app/logs -v /app/node_modules --rm -p 80:80 --network goals-net goals-node


b. 소스 코드 수정 시, 재 실행하기!

  • package.json 파일에 ‘nodemon’ 라이브러리를 추가하기!(이것은 NoedeJS에서만 사용 가능)


c. Dockerfile에 환경변수 만들어서 사용하기


  • Dockerfile

FROM node

WORKDIR /app

COPY package.json .

RUN npm install

COPY . .

EXPOSE 80

ENV MONGODB_USERNAME=root
ENV MONGODB_PASSWORD=secret

CMD [ "npm", "start" ]


  • 명령어 :
mongodb://${process.env.MONGODB_USERNAME}:${process.env.MONGODB_PASSWORD}@mongodb:27017/course-goals?authSource=admin


d. dockerignore

  • 컨테이너 내부에 이미 설치한 모든 종속성을 불필요하게 다시 복사하지 않도록 하기 위해서 설정한다.


  • 이미지를 리빌드하면 이러한 중복 파일이 복사되지 않고 이미지가 빌드될 거라 보장할 수 있다.


  • .dockerignore의 내용들
node_modules
Dockerfile
.git



5) (바인드 마운트로) React 컨테이너에 대한 라이브 소스 코드 업데이트하기**

  • ‘바인딩마운트’가 필요해서 절대경로로 ‘바인딩마운트’ 한 개 추가해주기

docker run -v /Users/maximilianschwarzmuller/development/teaching/udemy/docker-complete/frontend/src:/app/src --name goals-frontend  --rm -p 3000:3000 -it goals-react



  • 그리고 ‘npm install’에 의해 설치된 최신 종속성이 아니라 오래된 node_modules 폴더를 복사할 위험도 있다.


  • .dockerignore의 내용들
node_modules
Dockerfile
.git



2. Docker Compose : 다중 컨테이너

0) Docker Compose 설치

  • 1) VScode에서 Docker 플러그인 설치


  • 2) yaml 파일 1개를 만들어서 docker-compose의 version 설정하여 사용하기
    • 공식 홈페이지에서 버전 살펴보기



1) Docker Compose 개념

  • 도커 컴포즈는 ‘docker build’와 ‘docker run’ 명령을 대체할 수 있는 도구이다.


  • 다수의 ‘docker build’ 명령과 다수의 ‘docker run’을 단, 하나의 구성 파일로 가진다.


  • 그것은 모든 서비스 모든 컨테이너를 즉시 시작하고 필요하다면 모든 필요한 이미지를 빌드하는 오케스트레이션 명령 셋(set)이다. (Orchestration, 자동화된 설정, 구글링 해보기!)


  • 하나의 명령을 사용하여 모든 것을 중지하고 모든 것을 중단할 수도 있다. 우리가 선택한 텍스트 파일에 도커 명령을 저장할 수 있기 때문에 이것은 굉장하고 중요하다.


  • 구성 파일을 활용한 단 하나의 명령으로 전체 다중 컨테이너 애플리케이션을 시작하거나 중단할 수 있다.


  • 참고적으로 단일 컨테이너 애플리케이션에서도 도커 컴포즈를 사용할 수 있다. 하지만, 컨테이너가 여러 개인 경우에 가장 유용하다.



a. 특징 :

  • 도커 컴포즈는 커스텀 이미지를 위한 Dockerfile을 대체하지 않는다. 함께 동작한다.


  • 도커 컴포즈는 또한 이미지나 컨테이너를 대체하지 않는다.


  • 하지만, 도커 컴포즈는 다수의 호스트에서 다중 컨테이너를 관리하는데는 적합하지 않다.


  • 도커 컴포즈는 하나의 동일한 호스트에서 다중 컨테이너를 관리하는데 정말 좋다.


  • 단, 하나의 컴퓨터인 호스팅 머신에서 모든 것을 실행하고 있다. 현재 하나의 호스트이므로 도커 컴포즈를 사용하는데 딱 알맞다.



b. 핵심

  • 가장 중요한 요소 항목은 이른바 서비스(Service)라 불리우는 것이다. 이는 결국 다중 컨테이너 애플리케이션을 구성하는 컨테이너라고 할 수 있다.


  • 모든 서비스 아래에서 해당 서비스를 구성할 수 있다. 여기서 서비스라고 하면 실제로 컨테이너를 의미하는 것이다.


  • Docker Compose로 할 수 있는 것들** :
    • 게시해야 하는 포트를 정의하고 이 컨테이너에 필요한 환경 변수를 정의할 수 있다.
    • 이 컨테이너에 할당해야 하는 볼륨을 정의할 수 있다.
    • 네트워크를 할당할 수 있다.


  • 정리하면, 기본적으로 터미널에서 도커 명령으로 할 수 있는 모든 것을 도커 컴포즈에서 할 수 있다.


  • 터미널에서 이러한 도커 명령을 실행하는 것을 대체하는 것이 컴포즈의 아이디어이기 때문이다.



c. Docker Compose 설정 간단히 정리


  • 명령어 정리 :
    • 환경 변수를 environment뿐만 아니라 env_file 설정에 ‘.env’라는 외부 파일에도 저장 가능
    • 직접적으로 의존적인 컨테이너 연결 시키기 : depends_on:
    • 볼륨 설정 : 앞에서 볼륨을 생성하고 마지막의 볼륨 설정에 볼륨명을 추가해줘야 한다.
    • 인터렉티브 모드(개방적 입력 가능) : stdin_open: true
    • 터미널 실행(해당 터미널에 연결) : tty: true
    • port 번호** : 호스트 머신의 포트 3000에 포트 3000을 연결(ex) 3000:3000)


  • ‘build: ‘ 설정 :
    • Dockerfile이 다른 중첩 폴더에 있고 그 중첩 폴더 외부의 폴더에 액세스해야 하는 경우,
    • 그러면 더 높은 수준의 폴더로 설정되어야 한다.
    • 그리고 그런 경우는 매우 헷갈립니다. 나중에 여러 컨테이너를 이용할 때, 사용할 것이다.


  • ‘args: ‘ 설정 :
    • 경로가 더 긴 형태에서는 여기에 args를 추가하여 그 아래에서 some-arg에 특정 값을 추가할 수 있다.
    • Dockerfile이 ARGS를 사용하는 경우가 있는데 도커 컴포즈를 사용할 때, 이렇게 args를 지정할 수 있다.


  • ‘container_name: ‘ 설정 :
    • 컨테이너 이름도 명명된 이름으로 설정할 수 있다.


  • 중요** : front 프로젝트에는 바인딩 마운트가 1개 있는데 이제는 상대경로로 적어주어도 인식한다.(절대경로 X)
    • 여기에 frontend의 src폴더를 컨테이너 내부의 app폴더 내의 src 폴더에 바인딩하고자 합니다.
    • 이렇게 하면 바인드 마운트가 설정되며 그것으로 코드 변경 사항이 실행 중인 컨테이너에 즉시 반영됩니다.


  • depends_on 설정** : 프론트 설정에 depends_on을 추가하고 backend에 의존하도록 합니다.
    • backend가 시작된 경우에만 frontend를 시작하도록 말이죠.
    • 애플리케이션에 그러한 의존 설정이 필요한지 아닌지는 여러분에게 달려 있습니다.
    • 백엔드가 시작하여 작동되지 않더라도 작동하는 애플리케이션이 있을 수 있으니까요.



d. Docker Compose 설정의 미리 보기 :


  • docker-compose.yaml
version: "3.8"
services:
  mongodb:
    images: 'mongo'
    volumes:
      - data:/data/db 
    container_name: mongodb
      # environment: env 파일로 대체
      #   MONGODB_INITDB_ROOT_USERNAME: max
      #   MONGODB_INITDB_ROOT_PASSWORD: secret
        # - MONGODB_INITDB_ROOT_USERNAME=max
      env_file:
        - ./env/mongo.env
        
  backend:
    build: ./backend
    # build:
    #   context: ./backend
    #   dockerfile: Dockerfile
    #   args:
    #     some-arg: 1 
    ports:
      - '80:80'
    volumes:
      - logs:/app/logs
      - ./backend:/app
      -  /app/node_modules
    env_file:
      - ./env/backend.env
    depends_on:
      - mongodb
  frontend:
    build: ./frontend
    ports:  
      - '3000:3000'
    volumes:
      - ./frontend/src:/app/src
    stdin_open: true
    tty: true
    depends_on:
      - backend      
  volumes: 
    data:
    logs: 


  • backend.env
MONGODB_USERNAME=max
MONGODB_PASSWORD=secret



2) Docker Compose 실행 방법 :

  • 모든 컨테이너 생성 및 실행
docker-compose up
docker-compose up -d


  • 모든 컨테이너 중지 및 삭제
docker-compose down



3) Docker Compose에서 이미지 빌드 및 컨테이너명 지정

a. 이미지 강제 리빌딩 하기

  • ‘docker-compose up’명령에 ‘–build’를 추가하여 이를 강제할 수 있다.


  • 도커 허브 또는 다른 레지스트리에서 가져오는 이미지 : docker-compose up build


  • 커스텀된 이미지로 생성 : docker-compose build
    • 자체 이미지의 경우, docker-compose 파일에서 찾을 수 있는 커스텀 이미지를 빌드하려는 경우 ‘docker-compose build’를 사용하여 이를 수행할 수 있다.



b. 도커 컴포즈 실행 시 , 이름 강제로 변경됨

  • 실행 중인 frondend, backend, mongo 컨테이너가 있다. 그리고, 그들의 이름도 볼 수 있다.


  • frontend 컨테이너의 이름은 ‘docker-complete_frontend_1’이다. 여기에 backend의 이름이 있고, mongo 컨테이너의 이름도 여기에 있지만 변경된 이름이다.


  • 이것은 우리가 도커 컴포즈에 정의한 이름이 아니라는 것이다. 기술적으로 단지 서비스의 이름일 뿐이며 컨테이너로 번역된다.



4) Docker Compose 실습**


  • yml 파일을 compose up하면 애플리케이션 테스트를 위한 모든 인프라가 가볍게 구성된다.


  • restart : 도커 컨테이너가 죽으면, 다시 실행하게 설정할지 말지에 관해 설정 가능


  • yml 구성 시, 띄어쓰기 주의(줄바꾸고 나서는 2칸, ‘:’ 뒤에는 1칸)


  • 각 이미지별 환경변수는 docker 공식 홈페이지에서 확인할 것(Environment 탭에서)


  • 특이하게도 rabbitmq 이미지의 경우는 별도의 네트워크가 필요하고 이것 역시 docker 공식 홈페이지 참고하기!



a. 실습 1

  • docker-compose.yml
version: "3.0"

services:
  db:
    image: mysql:latest
    volumes:
      - ./db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root_pass
      MYSQL_DATABASE: wordpress
      MYSQL_USER: docker_pro
      MYSQL_PASSWORD: docker_pro_pass
  
  app:
    depends_on: 
      - db
    image: wordpress:latest
    volumes:
      - ./app_data:/var/www/html
    ports:
      - "8000:80"
    restart: always
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: docker_pro
      WORDPRESS_DB_PASSWORD: docker_pro_pass 


  • docker CLI
docker compose up --build



b. 실습 2

  • local-infra.yml
version: '3.0'

services:
  mariadb10:
    image: mariadb:10
    ports:
     - "3310:3306/tcp"
    environment:
      - MYSQL_ROOT_PASSWORD=my_db_passward
      - MYSQL_USER=docker_pro
      - MYSQL_PASSWORD=docker_pro_pass
      - MYSQL_DATABASE=docker_pro
  redis:
    image: redis
    command: redis-server --port 6379
    restart: always
    ports:
      - 6379:6379
  rabbitmq:
    image: rabbitmq:3-management-alpine
    container_name: 'rabbitmq'
    ports:
        - 5672:5672
        - 15672:15672
    volumes:
        - ~/.docker-conf/rabbitmq/data/:/var/lib/rabbitmq/
        - ~/.docker-conf/rabbitmq/log/:/var/log/rabbitmq
    networks:
        - rabbitmq_go_net

networks:
  rabbitmq_go_net:
    driver: bridge


  • docker CLI
docker compose -f local-infra.yml up --build



3. “유틸리티 컨테이너”로 작업하기 & 컨테이너에서 명령 실행하기

1) 유틸리티 컨테이너 개념




4. AWS , Git Actions 개념

1) AWS ECS

  • AWS에서 클러스터 먼저 만들기


a. ECR

  • 이미지를 받아오는 저장소 -> ECR
    • 도커는 너무 공개적이라서 private하게 ECR에 저장할 수 있다.


b. ECS, ELB

  • 우리는 서비스를 하기 위해서는 로드밸런서가 필요하다.


  • Task Definitions를 하나의 그룹으로 배포하기 위해서 로드밸런서를 해야하는데 처음에는 어디다가 로드를 지정을 할 것인가를 지정 하기위해 다음의 방식이 필요하다.


  • ELB 정책으로서, 다음 내용을 제거하는 이유 :
    • 먼저, Task Definitions는 이미지가 아무거나 바라보도록 임의적으로 만들고 그다음에 정책에 의해서 기존의 타겟과 리스너를 삭제하고 다시 서비스를 만들면서 넣어준다.



2) Git Actions : CI/CD

a. 개념

  • 코드를 실행해보고 에러가 없으면, 러너들이 코드들을 빌드도 해주고 테스트도 해주고 배포까지 해준다.


  • 러너들이 ECS의 파일들을 가지고 해당 package.json 파일 설정으로 우리의 깃헙파일에 올려준다. 이러한 Task Definitions을 보고 ECS에 배포를 해준다.


b. 사용법

  • 1) yaml 파일과 package.json 파일을 만들어서 커밋 체인지를 진행한다.


  • 2) yaml 파일은 해당 경로인 path에 해당 package.json 파일을 직접 만들어서 진행한다.


  • 3) AWS key와 같이 비밀키는 GitHub 설정 탭에서 추가로 설정한다.


  • 4) 러너가 Git Actions을 동작하려고 할 때, 3)에서 설정했던 시크릿 코드를 읽어서 AWS ECS를 실행하여 CI/CD를 하게된다.


  • 4) 이러한 Git Actions 설정들에 의해서 코드의 빌드, 테스트, 배포등은 러너가 알아서 동작한다.


  • 5) 이러한 과정은 ELB처럼 Task Definitions가 우리가 만든 이미지를 바라보도록 설정해서 서비스를 동작하도록 하는 과정이다.


c. 추가 질문


  • 혹시 SPRING으로 MSA(Elasticsearch)등등을 구축할려고할때 EC2 컨테이너 각각 한개씩 사용하나요?
    • 이렇게 사용하면, 비용 측면에서 효율이 안좋아서 ECS fargate 형식을 주로 이용한다.