[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 인스턴스가 통신 가능하도록 한다.
댓글남기기