[Docker] 도커와 CI환경
들어가며
해당 게시글은 인프런 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명령어 사용 불가: 해당 환경에서 실행 가능한 명령어만 입력
 
- 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 .(./ . 는 현재 디렉토리를 의미)
- 이미지 만드는 순서
    - FROM 베이스 이미지로 임시 컨테이너 생성
- 이미지에 다른 레이어가 존재하면 해당 레이어의 파일 스냅샷 또한 컨테이너에 추가
- 시작시 실행할 명령어(CMD)를 컨테이너에 추가
- 이를 토대로 새로운 이미지를 만들고 임시 컨테이너 삭제
 
- 즉 이미지로 컨테이너를 만들 수 있고, 컨테이너로 이미지도 만들 수 있다.
이미지 이름 부여
- 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를 이용하면 원격 저장소에 있는 프로젝트를 특정 이벤트에 따라 자동으로 테스트, 빌드하거나 배포할 수 있다.
- 로컬저장소에 있는 소스를 깃허브에 push한다
- Travis CI에게 소스가 push 되었다고 알려준다.
- Travis CI는 업데이트 된 소스를 깃허브에서 가져온다.
- 깃허브에서 가져온 소스를 테스트 코드를 실행해본다.
- 테스트가 성공하면 AWS같은 호스팅 사이트로 보내서 배포한다.
.travis.yml 파일 작성하기 (테스트까지)
- 도커환경에서 리액트앱을 실행하고 있으니 Travis CI에서도 도커환경 구성
- 구성된 도커 환경에서 Dockerfile.dev를 이용해서 도커 이미지 생성
- 어떻게 Test를 수행할 것이지 설정해주기
- 어떻게 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파일에서 불러오는 형태를 취해야 한다.
 
 
- IAM USER 생성: Identity and Access Management의 약자로써 AWS 리소스에 대한 액세스를 안전하게 제어할 수 있는 웹 서비스이다. IAM을 사용하여 리소스를 사용하도록 인증 및 인가된 대상을 제어한다.
        
- 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 인스턴스가 통신 가능하도록 한다.
 
 
      
    
댓글남기기