Skip to content

Instantly share code, notes, and snippets.

@novelview9
Created November 3, 2017 08:55
Show Gist options
  • Save novelview9/5ad849b828469a70a655d487ea222f4e to your computer and use it in GitHub Desktop.
Save novelview9/5ad849b828469a70a655d487ea222f4e to your computer and use it in GitHub Desktop.
test.md
# [변역[Execellent한 Dockerfile을 작성하는 법!(1)
번역에 앞서
지난 10월부터 aws 서울 리전에서도 ecs 서비스가 이용 가능하게 되었다.
올초 처음으로 도커를 접하고 ecs를 통해 배포 한 경험이 있었는데,
Aws 서울리전 출시 소식에 지금 일하고 있는 곳에서도 codedeploy로 배포하던 것을 ecs를 통해 배포하려고 했다.
관련해서 여러 문서들을 찾아 보던 중 여러 좋은 자료들을 발견했다.
[Docker (Compose) 활용법 - 개발 환경 구성하기](http://raccoonyy.github.io/docker-usages-for-dev-environment-setup/)
[초보를 위한 도커 안내서 - 도커란 무엇인가?](https://subicura.com/2017/01/19/docker-guide-for-beginners-1.html)
[AWS EC2 Container Service(ECS) (1) - 구조와 특징](http://bluese05.tistory.com/52)
[AWS EC2 Container Registry(ECR) 어렵지 않아요](http://bluese05.tistory.com/51)
[How to write excellent Dockerfiles](https://rock-it.pl/how-to-write-excellent-dockerfiles/)
그중 Dockerfile을 작성하는 법과 관련하여 도커 공식 문서의 최신명세를 따르면서 bad case를 소개하고 하나하나씩 설명하면서 좋은 방법으로 고쳐나가는 친절한 글을 발견했다.
너무 좋은 글이라 번역할 수 밖에 없었는데,
번역을 허락해준 Jakub Skałecki 님께 다시한번 감사의 인사를 전합니다.
Prior to translation,
From last October, AWS-ECS service launched in aws Seoul region.
Earlier this year, I learned and used docker first time, and distributing it through ecs
On that news that ecs launched in seoul region, I tried to change the distribute way of my service from github-hook & codedeploy to docker ECS
I looked up several documents related to it and found many good posts.
As for how to write Dockerfile, I read really good post
followed up with the latest spec of the official docs of the Docker, introduced a bad case, explained one by one, and modified in a good way.
This post is excellent as the title that I had to translate it into korean.
Thank you again to Jakub Skałecki for allowing me to translate this post
번역 : https://rock-it.pl/how-to-write-excellent-dockerfiles/
- - - -
반갑습니다. 저는 요즘 도커로 일을 한답니다. Dockerfile 을 작성하는것은 도커로 일을 하기 위한 가장 필수적인 부분인데, 이 부분에 있어서 몇가지 팁을 공유 하고자합니다.
### 목표는:
이미지 크기, 빌드시간 및 레이어 수를 최소화 하고자 합니다.
빌드 캐시를 최대한 사용하고 Dockerfile 의 가독성을 높이고자 합니다.
컨테이너를 쾌적하게 이용 가능 하도록 합니다.
## TL;DR (**Too long; didn’t read)
이 게시물은 예시와 디테일에 대한 설명으로 구성되었는데, 그 내용을 요악하자면 아래와 같습니다.
- `.dockerignore` 파일을 작성하세요
- 컨테이너는 한가지 일만 합니다.
- 도커 의 캐싱을 이해하세요. `COPY` 와 `RUN` 명령을 적절한 적절한 순서로 사용하세요.
- 여러 `RUN` 명령어행을 한줄로 합치세요.
- 각 단계 이후 불필요한 파일은 바로 제거하세요.
- 적절한 base image 를 사용하세요(alpine 버전이면 충분할겁니다.)
- `WORKDIR` 과 `CMD` 를 설정하세요.
- 한가지 이상의 명령어 또는 런타임데이터를 사용하여 파일을 업데이트 해야할 경우 `ENTRYPOINT`를 활용하세요.
- 컨테이너 진입 스크립트로 `exec` 를 사용하세요.
- `ADD`보다 `COPY` 를 선호합니다.
- Dockerfile 내부의 기본환경변수, 포트, 볼륨을 지정하세요
## 실제 적용 예제
이제 막 제 꿀팁들을 읽었을 겁니다. 대단합니다! 하지만 실제로 Dockerfiles 에 어떻게 적용하는지, 또 이렇게 해서 차이점이 뭐가 있냐? 라고 되물어 볼지도 모르겠네요.
저는 거의 모든 실수 사례들을 담은 간단한 Dockerfile 을 준비했습니다. 그리고 이걸 고쳐나갈거에요! 작은 노드 웹어플리케이션을 도커로 관리하고 싶다고 생각해봅시다. 아래를 보세요:(`CMD` 는 꼬여있고 작동되지 않을겁니다, 단지 예시일뿐입니다.)
```
FROM ubuntu
ADD . /app
RUN apt-get update
RUN apt-get upgrade -y
RUN apt-get install -y nodejs ssh mysql
RUN cd /app && npm install
# this should start three processes, mysql and ssh
# in the background and node app in foreground
# isn't it beautifully terrible? <3
CMD mysql & sshd & npm start
```
우리는 이렇게 도커 이미지를 만들겠죠. `docker build -t wtf .`
혹시 여기 모든 실수들을 집을 수 있나요? 아닌가요? 자 그럼 하나 하나씩 함께 고쳐나갑시다.
## 1. .dockerignore 를 작성하세요
이미지를 만들 때 Docker는 먼저 `context`을 준비해야합니다. 다시 말해 만드는 과정에 필요한 모든 파일을 수집해야합니다. 기본 conext는 Dockerfile 디렉토리의 모든 파일을 포함합니다. 대게는 .git 디렉토리, 다운로드한 라이브러리, 컴파일한 파일을 포함시키고 싶지 않을겁니다. .dockerignore 파일은 .gitignore 파일과 거의 똑같습니다. 예를 들면 다음과 같습니다.
```
.git/
node_modules/
dist/
```
## 2. 하나의 컨테이너에서 한가지 일만 하세요
기술적으로는 도커 컨테이너에서 여러 프로세스를 시작 **할 수는** 있습니다. 데이터베이스와 프론트엔드, 백엔드 어플리케이션, ssh, supervisor를 한 이미지에 **담을 수는** 있습니다. 하지만 이게 **당신을 괴롭힐거에요.**
- 늘어난 빌드 시간 (예: 프론트 엔드의 변경으로 인해 백엔드 전체를 강제로 다시 작성합니다.)
- 매우 덩치큰 이미지
- 많은 어플리케이션들로들로 부터 나오는 어려운 로그 관리(간단한 stdout을 사용하지 못합니다)
- 수평적인 스케일링시 생기는 낭비
- 좀비프로세스 문제 - 적절한 init 프로세스를 기억해야 합니다.
제 조언은 각 구성 요소에 대해 별도의 Docker 이미지를 준비하고 [Docker Compose](https://docs.docker.com/compose/)를 사용해 손쉽게 여러 컨테이너를 동시에 시작하는 것입니다.
Dockerfile에서 불필요한 패키지를 제거해 봅시다. SSH는 [docker exec](https://docs.docker.com/engine/reference/commandline/exec/)로 대체 할 수 있겠네요.
```
FROM ubuntu
ADD . /app
RUN apt-get update
RUN apt-get upgrade -y
# we should remove ssh and mysql, and use
# separate container for database
RUN apt-get install -y nodejs # ssh mysql
RUN cd /app && npm install
CMD npm start
```
## 3. 여러 `RUN` 명령어행을 한줄로 합치세요.
도커는 레이어로 이루어졌습니다. 이것이 어떻게 작동하는지에 대해 필수적으로 알아야 합니다.
- Dockerfile의 각 커맨드라인은 소위 레이어를 만듭니다.
- 레이어는 캐시되고 재사용됩니다.
- 한 레이어의 캐시가 무효로 처리 된다면, 그 이후 모든 후속 레이어들 또한 무효로 처리됩니다.
- 명령어 변경 후 예를들어 복사한 파일이 다르거나, 빌드 변수가 이전과 다를 경우 무효로 처리됩니다.
- 레이어는 변하지 않기 때문에, 레이어 위에 파일을 더하고 그 이후에 삭제하면, 이미지는 **여전히** 그 파일을 가지고 있습니다.(컨테이너에서는 이용할 수는 없겠죠)
도커 이미지를 양파에 비유해보고 싶네요.
https://lizclong.files.wordpress.com/2012/06/577815734_f19c326a03.jpg
둘다 당신을 울게 만들거에요… 아 이게 아니군요 흠흠. 둘다 레이어를 가지고 있습니다. 안쪽 레이어로 들어가거나 바꾸고싶다면, 그 이전것들을 없애야 합니다. 이것만 기억한다면 모든게 OK 입니다.
이 예제들을 최적화해봅시다. **우리는 모든 RUN 명령어를 하나로 만들겁니다.** 그리고 우리의 빌드과정에 문제를 만들 수 있는 apt-get upgrade 명령을 제거합니다.(베이스 이미지의 apt-get upgrade에 의존 하고 있습니다.)
```
FROM ubuntu
ADD . /app
RUN apt-get update \
&& apt-get install -y nodejs \
&& cd /app \
&& npm install
CMD npm start
```
명령을 변경 단위에 따라 합쳐야 한다는 점을 명심하세요. 현재 위 코드는, 소스 코드가 변경 될 때마다 apt-get 부터 전체 Nodejs 를 다시설치 하는 과정을 거칩니다. 따라서 아래와 같이 작성하는 것이 더 나은 방법입니다.
```
FROM ubuntu
RUN apt-get update && apt-get install -y nodejs
ADD . /app
RUN cd /app && npm install
CMD npm start
```
## 4. 베이스 이미지 태그를 'latest’로 사용하지 않습니다.
`latest` 태그는 기본값으로, 다른 태그가 지정되지 않은 경우 적용됩니다. 다시말해 `FROM ubuntu` 태그는 `FROM ubuntu:latest` 와 정확히 같습니다. 그러나 ‘latest’태그 자체가 새버전이 나왔을 경우 다른 이미지를 가리키고, 이때문에 빌드가 중단 될 수도 있습니다. 따라서 기본 이미지를 최신 상태로 유지해야하는 Dockerfile을 만드는 경우가 아니라면 특정한 태그를 적어주세요.
예제에서 `16.04` 태그를 사용해봅시다.
```
FROM ubuntu:16.04 # it's that easy!
RUN apt-get update && apt-get install -y nodejs
ADD . /app
RUN cd /app && npm install
CMD npm start
```
## 5. 각각 *RUN* 단계 이후 불필요한 파일은 바로 제거하세요.
지금까지 apt-get 소스를 업데이트하고, 컴파일에 필요한 몇 개의 패키지를 설치하고, 다운로드 후 압축을 풀었습니다. 이것들은 분명 최종 이미지에선 필요가 없기 때문에 제거하도록 하겠습니다. **용량** 때문에요!
예제에서 apt-get lists(`apt-get update`명렁 시 생긴 것들)를 제거 해 보겠습니다.
```
FROM ubuntu:16.04
RUN apt-get update \
&& apt-get install -y nodejs \
# added lines
&& rm -rf /var/lib/apt/lists/*
ADD . /app
RUN cd /app && npm install
CMD npm start
```
## 6. 적절한 베이스 이미지 사용
예제에서는 `ubuntu`이미지를 사용했습니다. 하지만 왜 그랬을까요? node 애플리케이션을 실행하고 싶을 때 `ubuntu`와 같은 일반적인 목적의 베이스 이미지를 사용해야 할까요? 더 나은 옵션은 node가 이미 설치되어있는 특수 이미지를 사용하는 것입니다.
```
FROM node
ADD . /app
# we don't need to install node
# anymore and use apt-get
RUN cd /app && npm install
CMD npm start
```
또는 alpine 버전을 선택하는것이 더나은 방법일 수 있습니다. (alpine은 크기가 약 4MB에 불과한 아주 작은 리눅스 배포판입니다. 기본 이미지로 사용하기에 안성맞춤입니다.)
```
FROM node:7-alpine
ADD . /app
RUN cd /app && npm install
CMD npm start
```
Alpine에는 [apk](https://wiki.alpinelinux.org/wiki/Alpine_Linux_package_management)라는 패키지 관리자가 있습니다. `apt-get`과 약간 다르지만 아주 배우기 쉽습니다. 또한 `—o-cache`, `--virtual` 옵션과 같은 정말 유용한 유용한 기능이 있습니다.이와 같이 우리는 이미지 요구사항에 따라 더도 말고 꼭 알맞은 이미지를 선택합시다. 당신의 디스크가 당신을 사랑합니다 :)
포스팅이 길어어서 6번 이후는 두번째 글로 빠른 시일내에 올리겠습니다.
읽어주셔서 감사합니다.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment