[DevOps] Dockerfile 최적화
[DevOps] Dockerfile 최적화
안녕하세요? 정리하는 개발자 워니즈 입니다. 이번시간에는 Dockerfile Optimization(도커파일 최적화) 에 대해서 정리를 해보도록 하겠습니다. 이번 내용은 도커파일을 효율적으로 작성하기 위해서 중요한 다섯 가지 영역들에 대해서 정리를 해볼 예정입니다.
빌드 시간 줄이기
1. 순서는 캐싱이 중요하다.
도커 명령어를 수행하는 순서는 매우 중요합니다. 왜냐하면 특정 스텝에서 파일의 변화나 도커 파일의 수정 등으로 캐시가 무효화 될 경우 이어지는 스텝들의 캐시가 모두 깨져버리기 때문입니다. 그러므로 가장 변하지 않는 스텝을 먼저, 자주 변경 되는 스텝은 아래로 배치하는 것이 캐싱을 최적화 시킵니다.
2. 더 구체적인 COPY는 캐시 부담을 줄여 준다.
파일들을 도커 이미지로 복사할 때, 당신이 무엇을 카피 하고자 하는지를 최대한 명확하게 하세요. 이미지에 복사된 파일들 가운데 하나라도 변경될 경우 캐시가 무효화 됩니다. 위의 예시에서는 이미 빌드를 마친 jar 파일만 복사하는 것을 볼 수 있습니다. 이 경우 관련이 없는 파일들의 변화는 캐시에 영향을 주지 않습니다.
3. 캐시할 수 있는 단위들을 구별해내라.
각각의 RUN 명령어들은 캐시할 수 있는 단위로 볼 수 있습니다. 너무 많은 캐시 단위들은 불필요합니다만, 하나의 RUN 명령어에 모든 명령어를 다 연결시키는 것 역시 캐시가 쉽게 무효화 되게 하여 개발 사이클에 좋지못합니다. 패키지 매니저로부터 패키지들을 다운받을 때에 항상 업데이트와 인스톨을 하나의 RUN안에 묶어주세요. 그러면 그들은 하나의 캐시 단위가 됩니다. 그렇지 않으면 예전 버전의 패키지를 다운 받을 위험이 있습니다.
이미지 크기 줄이기
4. 불필요한 의존성을 제거하라
불필요한 의존성은 제거하고 불필요한 라이브러리는 설치 하지 않습니다. apt와 같은 패키지 매니저는 자동으로 사용자가 특정된 패키지가 추천하는 것들을 설치하고, 이는 불필요한 내용들을 남깁니다. 따라서, no-install-recommends 플래그를 통해서 이들이 자동으로 설치되는 것을 막을 수 있습니다.
5. 패키지 매니저 캐시를 제거하라
패키지 매니저는 스스로의 캐시를 유지합니다. 이는 최종 도커 이미지에 그대로 남을 수 있습니다. 한 가지 해결 방안은 패키지를 설치한 RUN 명령어 안에서 캐시를 지워주는 것입니다. 다른 RUN 명령어에서 지워주는 것은 이미지의 크기를 줄여주지 못합니다.
유지 보수성
6. 가능하면 공식 이미지를 사용하라
공식 이미지는 유지 보수에 들어가는 시간을 많이 절감해줍니다. 왜냐하면 모든 설치 과정들이 완료되어 있고 best practice가 적용되었기 때문입니다. 만약 여러 프로젝트들을 가지고 있다면, 이러한 설치 과정이 적용된 layer들을 공유할 수 있습니다.
7. 구체적인 태그를 사용하라.
latest 태그는 사용하지 말아라. 이는 도커 허브의 공식 이미지들에 항상 적용할 수 있는 편의성은 있습니다. 하지만 시간이 흐름에 따라서 급격한 변화가 있을 수 있습니다. 이 경우 캐시가 없다면 빌드가 실패할 위험이 있습니다.
대신에 더 구체적인 태그를 사용하십시오. 예시 코드에서는 openjdk를 사용합니다. 많은 태그들이 가능하니, 모든 태그들을 리스팅 해놓은 openjdk 이미지의 도커허브 문서를 살펴보세요.
재현성
8. 일관된 환경에서 소스코드를 빌드하라.
예시 속 자바 어플리케이션의 경우 Maven과 JDK를 필요로 합니다. 그러므로 도커 허브의 공식 maven 이미지들 가운데 JDK를 포함하고, 크기가 가장 작은 것을 찾아 기초로 삼습니다. 만일 추가적으로 의존 라이브러리가 필요하다면 RUN 명령어를 통해 설치 합니다.
9. 빌드 의존성을 제거하기 위해서 다단계 빌드 (Multi-stage build)를 사용하라.
다단계 빌드란 여러개의 FROM 구문을 사용할 수 있습니다. 각각의 FROM은 새로운 단계를 시작합니다. AS 키워드로 이름을 붙일 수 있는데, 위 예시에서는 첫 단계에 builder라는 이름을 붙여서 나중에 다시 참조될 수 있도록 했습니다. 이는 우리의 모든 빌드 의존성들이 일관된 환경에 포함될 수 있도록 합니다.
두 번째 단계는 우리의 마지막이자 최종 이미지를 만들어내는 단계입니다. 여기서는 런 타임에 엄격하게 필요로 하는 것들만 포함하며, 예시에서는 알파인 리눅스 기반의 최소 JRE(Java Runtime)에 해당합니다. 중간의 builder 단계는 캐시되지만 최종 이미지에는 포함되지 않습니다. 중간 빌드 결과물들을 우리의 최종 이미지에 포함시키기 위해서는 COPY –from=STAGE_NAME 구문을 사용하며, 여기서 STAGE_NAME은 builder입니다.
마치며..
이번시간에는 Dockerfile의 최적화 내용에 대해서 정리를 해보았습니다. 필자가 속한 프로젝트에서도 여러개의 프로젝트가 있고, 개별적으로 Dockerfile을 사용하고 있습니다. 그러다보니 불필요하게 사용하는 부분이 있고, 이러한 부분들을 개선하기 위해서 내용을 정리 해보았습니다. 다음시간에는 실제로 어떤 내용들을 적용했는지 그부분에 대해서 정리를 해보도록 하겠습니다.
참조
도커 파일(Dockerfile) 작성 Best Practices