[Docker] 도커와 CI환경

10 분 소요

들어가며

해당 게시글은 인프런 John Ahn 강사님의 따라하며 배우는 도커와 CI환경 강의를 바탕으로 쓰였음을 미리 밝힙니다.

도커 기본

도커를 쓰는 이유

  • Installer를 통한 설치의 경우 갖고 있는 서버, 패키지 버전, 운영체제 등에 따라 프로그램을 설치하는 과정중에 많은 에러들이 발생한다. 또한 그 과정이 복합하다.
  • 도커를 이용하여 예상치 못한 에러를 줄일 수 있으며 그 과정이 매우 간단하다.

도커란 무엇인가?

  • 컨테이너를 사용하여 응용프로그램을 더 쉽게 만들고 배포하고 실행할 수 있도록 설계된 도구이며 컨테이너 기반의 오프소스 가상화 플랫폼을 말한다.
  • 다양한 프로그램, 실행환경을 컨테이너로 추상화하고 동일한 인터페이스를 제공하여 프로그램의 배포 및 관리를 단순하게 해준다. AWS, Google cloud 등 어디에서든 실행 가능하게 해준다.

도커 이미지와 도커 컨테이너 정의

  • 컨테이너는 코드와 모든 종속성을 패키지화하여 응용 프로그램이 한 컴퓨팅 환경에서 다른 컴퓨팅 환경으로 빠르고 안정적으로 실행되도록 하는 스프트웨어의 표준 단위
  • 이미지는 코드, 런타임, 시스템 도구, 시스템 라이브러리 및 설정과 같은 응용 프로그램을 실행하는 데 필요한 모든 것을 포함하는 가볍고 독립적이며 실행 가능한 소프트웨어 패키지
  • 컨테이너는 소프트웨어를 환경으로부터 격리시키고 개발과 스테이징의 차이에도 불구하고 균일하게 작동하도록 보장

도커를 사용할 때의 흐름

먼저 도커 CLI에 커맨드를 입력 -> 도커 서버(도커 데몬)이 그 커맨드를 받아서 그것에 따라 이미지를 생성하든 컨테이너를 실행하든 모든 작업을 하게된다. -> 이미지 캐쉬 보관 장소에 해당되는 이미지가 없다면 도커허브에서 이미지를 pull

도커와 기존의 가상화 기술과의 차이를 통한 컨테이너 이해

  • 하이퍼 바이저 기반의 VM 구조: 하드웨어 및 호스트 OS 위에 하이퍼바이저가 있고 그 위에 VM을 띄우고 여기서 게스트 OS를 띄운다. 하이퍼바이저에 의해 구동되는 각 VM은 CPU, MEMORY 등 하드웨어 자원을 게스트 OS에 에뮬레이트 하는 방식으로 할당받는데 오버헤드가 크다.
  • 도커 컨테이너의 구조: 하이퍼바이저와 게스트 OS가 필요하지 않아 가볍다. 호스트 OS위에 이미지를 통한 컨테이너를 띄운다. 다른 컨테이너와 호스트 OS의 동일한 커널을 공유한다. 따라서 컨테이너 내부에서 실행되는 프로세스는 호스트 시스템에서 디버그나 로깅이 가능하다.
  • 컨테이너 격리 기술: 리눅스의 가상화 기술
    • C Group: CPU, 메모리 등 프로세스 그룹의 시스템 리소스 사용량을 관리
    • 네임스페이스: 하나의 시스템에서 프로세스를 격리시키는 가상화 기술, 별개의 독립된 공간을 사용하는 것처럼 격리된 환경을 제공하는 경량 프로세스 가상화 기술

이미지로 컨테이너 만들기

  • 이미지: 시작시 실행 될 명령어(run maraidb) + 파일 스냅샷(mariadb 파일)
  • 파일 스냅샷을 컨테이너로 카피 후에 명령어도 카피하여 maradb를 실행

리눅스의 Cgroup, 네임스페이스 커널 기능을 쓸 수 있는 이유

  • Cgroup 기능을 통해 호스트의 리소스를 컨테이너 단위로 할당하고 네임스페이스 기능을 통해 프로세스를 컨테이너에 격리

기본적인 도커 클라이언트 명령어

도커 이미지 내부 파일 구조

  • docker run 이미지이름
    • docker: 도커 클라이언트 언급
    • run: 컨테이너 생성 및 실행
    • 이미지이름: 컨테이너를 위한 이미지
  • docker run 이미지이름 ls
    • ls: 원래 이미지가 가지고 있는 시작 명령어(run hello-world)를 무시하고 여기에 있는 커맨드를 실행
    • hello-world이미지로는 ls 명령어 사용 불가: 해당 환경에서 실행 가능한 명령어만 입력

컨테이너 나열

  • CTAINER ID + IMAGE + COMMAND + CREATED + STATUS + PORTS + NAMES
    • COMMAND: 컨테이너 시작시 실행될 명령어, 대부분 이미지에 내장되어 있어 별도 설정이 필요 없다.
    • PORTS: 컨테이너가 개방한 포트와 호스트에 연결한 포트
  • 원하는 항목만 보기: docker ps --format 'table\table'

도커 컨테이너의 생명주기

  • docker run: docker create 이미지이름 + docker start 컨테이너아이디/이름
    • create: 이미지의 파일스냅샷을 컨테이너를 생성하고 할당된 하드디스크에 카피
    • start: 이미지 명령어를 실행
    • docker start -a 이미지이름: -a 옵션의 경우 터미널에서 컨테이너 아웃풋을 볼 수 있다.

Docker Stop vs Docker Kill

  • docker stop: gracefully하게 중지 시킨다. 그동안 하던 작업을 마무리하고 중지 시킨다.
  • docker kill: 바로 중지 시킨다.

컨테이너 삭제하기

  • docker rm 아이디/이름: 실행중인 컨테이너는 먼저 중지한 후에 삭제 가능
  • docker rm docker ps -a -q: 모든 컨테이너 삭제
  • docker system prune: 한번에 모든 컨테이너, 이미지 삭제, 실행중인 컨테이너에는 영향을 주지 않음

실행 중인 컨테이너에 명령어 전달

  • docker exec 컨테이너아이디 명령어: 이미 실행중인 컨테이너에 명령어를 전달
  • docker run의 경우 컨테이너를 새로 생성항여 명령어를 실행
  • 컨테이너 CLI(셸 환경)를 실행시키려면 명령어 자리에 sh, bash, zsh 등을 넣으면 된다.

도커 이미지 만들어보기

도커 이미지 생성 순서

  • Dockerfile -> 도커 클라이언트 -> 도커 서버 -> 이미지 생성
    • Dockerfile: 이미지 생성을 위한 설정 파일, 컨테이너가 어떻게 행동해야 하는지에 대한 설정들을 정의
    • 도커 서버: 클라이언트에 전달된 작업을 처리

Dockerfile 만들기

  • 도커 파일 만드는 순서
    • 베이스 이미지를 명시(FROM)
    • 추가적으로 필요한 파일을 다운 받기 위한 몇가지 명령어를 명시(RUN)
    • 컨테이너 시작시 실행 될 명령어를 명시(CMD)
  • 베이스 이미지란
    • 도커 이미지는 여러개의 레이어로 되어있다. 그중에서 베이스 이미지는 기반이 되는 부분이다.
    • 레이어는 중간 단계의 이미지라고 생각하면 된다.

도커 파일로 도커 이미지 만들기

  • Dockerfile -> 도커 클라이언트 -> 도커 서버 -> 이미지
  • docker build ./ 또는 docker build .(./ . 는 현재 디렉토리를 의미)
  • 이미지 만드는 순서
    1. FROM 베이스 이미지로 임시 컨테이너 생성
    2. 이미지에 다른 레이어가 존재하면 해당 레이어의 파일 스냅샷 또한 컨테이너에 추가
    3. 시작시 실행할 명령어(CMD)를 컨테이너에 추가
    4. 이를 토대로 새로운 이미지를 만들고 임시 컨테이너 삭제
  • 즉 이미지로 컨테이너를 만들 수 있고, 컨테이너로 이미지도 만들 수 있다.

이미지 이름 부여

  • docker build -t 도커아이디/이미지이름:태그 ./

Docker Volumes

  • docker run -p 5000:8080 -v /usr/src/app/node_modules -v $(pwd):/usr/src/app 이미지 아이디
    • /usr/src/app/node_modules: 호스트 디렉토리에 없으므로 매핑 제외
    • pwd는 현재 디렉토리를 의미

Docker Compose

Docker Compose란

  • 다중 컨테이너 도커 애플리케이션을 정의하고 실행하기 위한 도구

레디스

  • 메모리 기반의 키-값 구조 데이터 관리 시스템이며, 데이터를 메모리에 저장하고 빠르게 조회할 수 있는 비관계형 데이터베이스이다.
  • 메모리에 저장을 하기 때문에 데이터를 저장하는것과 로딩하는데 훨씬 빠르게 처리할 수가 있으며, 비록 메모리에 저장하지만 영속적으로도 보관이 가능하다.
  • 레디스 클라이언트 생성
    • 도커 환경이 아닌 경우: host: "https://redis-server.com"
    • 도커 환경인 경우: host: "redis-server", docker-compose.yml 파일에 명시한 컨테이너 이름으로 주면된다.

도커 컨테이너 사이의 통신

  • docker-compose build: 이미지를 빌드하기만 하며 컨테이너를 시작하지 않는다.
  • docker-compose up: 이미지가 존재하지 않을 경우에만 빌드하며, 컨테이너를 시작한다.
  • docker-compose -d up: 백그라운드에서 실행
  • docker-compose up --build: 이미지를 강제로 빌드하며, 컨테이너를 시작한다.
  • docker-compose up -- no --build: 이미지 빌드 없이, 컨테이너를 시작한다. 이미지가 없을시 실패

간단한 어플 배포

리액트 앱 설치

  • npx create-react-app ./
  • 개발 단계: npm run start
  • 테스트 단계: npm run test
  • 배포 단계: npm run build

도커를 이용하여 리액트 앱 실행

  • Dockerfile.dev
FROM node:alpine

WORKDIR /usr/src/app

COPY package.json ./

RUN npm install // 여기서 node_modules 생성되므로 로컬에서는 node_modules 지우고 이를 통해 COPY에서 시간 단축

COPY ./ ./

CMD ["npm", "run", "start"]
  • docker build -f Dockerfile.dev ./: -f는 이미지를 빌드할 때 쓰일 도커 파일을 지정해준다.
  • package.json변경: "start": "react-scripts --openssl-legacy-provider start"

도커 볼륨을 이용한 소스 코드 변경

  • docker run -it -p 3000:3000 -v /usr/src/app/node_modules -v $(pwd):/usr/src/app react

도커 컴포즈 이용

version: "3"
services:
  react: 
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - /usr/src/app/node_modules
      - ./:/usr/src/app
    stdin_open: true

운영환경을 위한 Nginx

  • 개발 서버는 소스 변경시 자동으로 다시 빌드하여 변경 소스를 반영해줌으로써 개발 환경에 특화된 기능들이 있다. 하지만 운영 환경에서는 소스 변경시 바로 다시 반영해줄 필요가 없으며 개발에 필요한 기능들이 필요하지 않기에 더 깔끔하고 빠른 Nginx를 웹 서버로 사용한다.

운영환경 도커 이미지를 위한 Dockerfile 작성하기

  • npm run build로 생성한 빌드 파일을 Nginx 서버가 브라우저에서 보일수 있게 설정
FROM node:alpine as builder
WORKDIR /usr/src/app
COPY package.json ./
RUN npm install
COPY ./ ./
CMD ["npm", "run", "build"]

FROM nginx
COPY --from=builder /usr/src/app/build /usr/share/nginx/html
  • --from=builder: 다른 stage에 있는 파일을 복사할때 다른 stage 이름을 명시

간단한 어플을 실제로 배포해보기(테스트 & 배포 부분)

Travis CI 설명

  • Travis CI를 이용하면 원격 저장소에 있는 프로젝트를 특정 이벤트에 따라 자동으로 테스트, 빌드하거나 배포할 수 있다.
  1. 로컬저장소에 있는 소스를 깃허브에 push한다
  2. Travis CI에게 소스가 push 되었다고 알려준다.
  3. Travis CI는 업데이트 된 소스를 깃허브에서 가져온다.
  4. 깃허브에서 가져온 소스를 테스트 코드를 실행해본다.
  5. 테스트가 성공하면 AWS같은 호스팅 사이트로 보내서 배포한다.

.travis.yml 파일 작성하기 (테스트까지)

  1. 도커환경에서 리액트앱을 실행하고 있으니 Travis CI에서도 도커환경 구성
  2. 구성된 도커 환경에서 Dockerfile.dev를 이용해서 도커 이미지 생성
  3. 어떻게 Test를 수행할 것이지 설정해주기
  4. 어떻게 AWS에 소스코드를 배포할 것인지 설정해주기
sudo: required

language: generic

services:
  - docker

before_install:
  - echo "start creating an image with dockerfile"
  - docker build -t react -f Dockerfile.dev .

script:
  - docker run -e CI=true react npm run test -- --coverage // 실행할 스크립트(테스트 실행)

after_success:
  - echo "test success"

AWS 알아보기

  • EC2란: Amazon Elastic Compute Cloud(Amazon EC2)는 AWS 클라우드에서 확장식 컴퓨팅을 제공한다. Amazon EC2를 사용하면 하드웨어에 선투자할 필요가 없어 더 빠르게 앱을 개발하고 배포할 수 있다. Amazon EC2를 통해 원하는 만큼 가상 서버를 구축하고 보안 및 네트쿼크 구성과 스토리지 관리가 가능하다.
    • Amazon EC2는 한대의 컴퓨터를 임대한다고 생각하면 된다. 그리고 그 컴퓨터에 OS를 설치하고 웹서비스를 위한 프로그램들(WAS, DB)을 설치해서 사용할 수 있다. 1대의 컴퓨터를 하나의 EC2 인스턴스라고 부른다.
  • EB란: AWS Elastic Beanstalk는 Apache, nginx 같은 친숙한 서버에서 Java, NET. PHP, Node.js, Python, Ruby, GO 및 Docker와 함께 개발된 웹 응용 프로그램 및 서비스를 배포하고 확장하기 쉬운 서비스이다. EC2 인스턴스나 데이터베이스 같이 많은 것들을 포함한 환경을 구성하며 만들고 있는 소프트웨어를 업데이트 할때마다 자동으로 이 환경을 관리해준다.
  • Travis CI에서 도커 이미지 생성 -> 도커 허브 저장 -> 해당 이미지를 AWS EB에서 받아서 컨테이너 생성

Elastic Beanstalk 환경 구성하기

  • 로드 발란서 -> EC2 인스턴스(64bit Amazon Linux) -> 도커 컨테이너 -> 어플리케이션
  • 트래픽이 많을 경우 EB 환경에 있는 로드 발란서가 각 EC2로 트래픽을 분산

.travis.yml 파일 작성하기 (배포 부분)

sudo: required

language: generic

services:
  - docker

before_install:
  - echo "start creating an image with dockerfile"
  - docker build -t react -f Dockerfile.dev .

script:
  - docker run -e CI=true react npm run test -- --coverage // dockerfile의 CMD "npm" "run" "start" 를 덮어씀

deploy:
  provider: elasticbeanstalk
  region: "us-east-2"
  app: "react"
  env: "React-env"
  bucket_name: "elasticbeanstalk-us-east-2-601633539824"
  bucket_path: "react"
  on:
    all_branches: true
  • provider: 외부 서비스 표시
  • region: 현재 사용하고 있는 AWS의 서비스가 위치하고 있는 물리적 장소
  • app: 생성된 앱 이름
  • bucket_name: 해당 EB를 위한 s3 버켓 이름 (Travics CI는 가지고 있는 파일을 압축해서 먼저 s3에 보낸다.)
  • bucket_path: 앱 이름과 동일
  • branch: 어떤 브랜치에 push를 할때 AWS에 배포를 할것인지.

Travis CI의 AWS접근을 위한 API 생성

  • 깃허브 -> Travis CI -> AWS: Travis CI 아이디 로그인시 깃허브 연동으로 인증을 하고, AWS에서 제공해주는 secret key를 travis.yml 파일에다가 적어주면 된다.
  • Seceret, Access API Key 받는 순서
    • IAM USER 생성: Identity and Access Management의 약자로써 AWS 리소스에 대한 액세스를 안전하게 제어할 수 있는 웹 서비스이다. IAM을 사용하여 리소스를 사용하도록 인증 및 인가된 대상을 제어한다.
      • Root 생성자: 현재 우리가 처음 가입하여 사용하고 있는 계정, AWS 서비스 및 리소스에 대한 완전한 액세스 권한이 있음
      • IAM 사용자: root 사용자가 부여한 권한만 가지고 있음
    • API키를 yml 파일에 적어주기
      • 직접 키를 Travis.yml 파일에 적으면 노출 위험이 있기 때문에 Travis 웹사이트 해당 저장소에 저장하고 yml파일에서 불러오는 형태를 취해야 한다.
  • Amazon Linux2 플랫폼 사용시 EB는 docker-compose.yml 을 보고 처리한다.

복잡한 어플을 실제로 배포해보기(개발 환경 부분)

  • Nginx의 Proxy를 이용한 설계(요청 라우팅): client -> Nginx(80) -> location/ 의 경우 React에서 정적 리소스 처리(3000), location/api 의 경우 DB(3306)와 연동된 백엔드 서버에서 처리(5000)
  • Dockerfile.dev / Dockerfile: Front -> Server -> Mysql -> Nginx
  • Travis CI 에서 테스트 성공시 Dockerfile를 이용하여 이미지 생성후 도커 허브에 저장 -> EB 에서 해당 이미지를 통해 배포
version: "3"
services:
  frontend:
    build:
      dockerfile: Dockerfile.dev
      context: ./frontend
    volumes:
      - /app/node_modules
      - ./frontend:/app
    stdin_open: true

  nginx: // proxy 역할
    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:
    platform: linux/amd64
    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: johnahn
      MYSQL_DATABASE: myapp

복잡한 어플을 실제로 배포해보기(테스트 & 배포 부분)

  • 소스 파일을 github에 올린후 travis CI를 이용해서 테스트 후 도커 이미지를 빌드한다. 이후 해다 이미지를 docker hub에 올린후 AWS EB에서 pull하여 서비스를 배포한다.
  • .travis.yml
language: generic

sudo: required

services:
  - docker

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

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

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

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

  - docker push rere95/docker-frontend
  - docker push rere95/docker-backend
  - docker push rere95/docker-nginx

deploy:
  provider: elasticbeanstalk // 외부 서비스
  region: "ap-northeast-2" // 외부 서비스가 위치한 물리적 장소
  app: "docker-fullstack-app"
  env: "DockerFullstackApp-env"
  bucket_name: elasticbeanstalk-ap-northeast-2-972153559337
  bucket_path: "docker-fullstack-app"
  on:
    branch: master // 어떤 브랜치에 Push 할때 AWS에 배포할 것인지
  
  access_key_id: $AWS_ACCESS_KEY
  secret_access_key: $AWS_SECRET_ACCESS_KEY
  • docker-compose.yml
version: 2.4

services:
  frotend:
    image: rere95/docker-frontend
    volumes:
      - /app/node_modules
      - ./frontend:/app
    stdin_open: true
    mem limit: 128m

  nginx:
    restart: always
    image: rere95/docker-nginx
    ports:
      -80:80
    mem limit: 128m
    links:
      - frontend
      - backend
  
  backend:
    image: rere95/docker-backend
    container_name: app_backend
    volumes:
      - /app/node_modules
      - ./backend:/app
    environment:
      MYSQL_HOST: docker-fullstack-mysql.cmok.ap-northest-2.rds.com
      MYSQL_USER: root
      MYSQL_ROOT_PASSWORD: 1234
      MYSQL_DATABASE: myapp
      MYSQL_PORT: 3306
    mem limit: 128m

VPC(virtual private cloud)와 Security Group 설정하기

  • AWS와 RDS를 이용하여 MYSQL를 앱과 연결시켜야 하는데 그거을 하기위해서 VPC와 Security Group을 설정해주어야 한다.
  • VPC: AWS 클라우드에서 논리적으로 격리된 공간을 프로비저닝하여 고객이 정의하는 가상 네트워크에서 리소스를 시작할 수 있다. 내가 AWS에서 EC2, EB, RDS 인스턴스를 만들었다면 이러한 인스턴스들을 나의 아이디에서만 접근이 가능하게 논리적으로 격리된 네트워크에서 생성이 되게 해준다. 따라서 다른 아이디로는 접근 불가능하다.
  • Security Group
    • Inbound: 외부에서 EB 인스턴스로 요청을 보내는 트래픽을 말한다.(HTTP, HTTPS, SSH 등)
    • Outbound: EB 인스턴스에서 외부로 나가는 트래픽을 말한다. 파일을 다운로드 하거나 inbound로 들어온 트래픽을 처리하여 응답하는 경우도 포함된다.
    • 같은 VPC에서 오는 트래픽은 모두 허용하여 같은 VPC에 있는 EB 인스턴스와 RDS 인스턴스가 통신 가능하도록 한다.

태그: ,

카테고리:

업데이트:

댓글남기기