도커 베스트 프랙티스 (번역)

도커 베스트 프랙티스 (번역)

깃헙에는 현재 백만이 넘는 도커파일들이 있다. 하지만 모든 도커파일들이 동일하지는 않다. 효율성은 중요한 것이고, 이번 블로그 시리즈는 도커파일의 다섯 가지 영역: 빌드 시간의 증가, 이미지의 사이즈, 유지성, 보안, 반복성에 있어서 베스트 프랙티스를 다룰 것이다. 만약 독자가 도커에 막 시작하는 사람이라면, 이 글은 독자를 위한 글이다! 이후 시리즈는 좀 더 Advanced한 주제가 될 것이다.

Important note: 아래의 이 팁들은 메이븐을 기반으로한 자바 프로젝트의 도커파일을 향상시키는 일련의 과정이다. 마지막 도커파일이 결국에 추천하는 도커파일이 될 것이고, 모든 중간 과정들은, 구체적인 베스트 프랙티스를 설명하기 위한 과정이다.

Incremental build time

개발 사이클에서, 코드를 변경시키고, 다시 빌드 하면서 도커 이미지를 빌드할 때, 캐시를 활용하는 것은 중요하다. 캐싱은 불필요한 빌드 과정을 동작하는 것을 피하게 만든다.

Tip #1: 캐싱에는 순서가 중요하다.

빌드 단계 (도커파일의 Instructions)의 순서는 중요한데, 왜냐하면 하나의 스탭의 캐시가, 파일이 바뀌거나, 도커파일의 라인을 수정함으로써 유효하지 않게 되었을 때, 이어지는 단계들의 캐시가 깨질 것이기 때문이다. 너의 Instruction 순서를 최소로 수정되는 것에서 자주 수정되는 순서대로 정렬해야 캐싱을 최적화 할 수 있다.

Tip #2: 캐시가 깨지는 것을 제한하기 위해서 더 구체적인 COPY를 사용해라

꼭 필요한 것만 카피해라. 되도록 COPY .와 같은 인스트럭션은 피해라. 너의 이미지에 파일들을 카피해 넣을 때, 너가 아주 구체적으로 너가 원하는 카피 대상을 지정하도록 해라. 카피한 어떠한 파일의 변경점은 캐시를 무효화 한다. 위 예시에서, 오직 미리 빌드된 jar 앱만 이미지 내에서 필요하다. 그러므로 딱 그것만 카피해라. 이 방법으로 연관 없는 파일의 변경점은 캐시에 영향을 주지 않게 된다.

Tip #3: apt-get update & install과 같은 캐싱 가능한 단위를 일원화 해라

RUN 인스트럭션은 캐싱 가능한 실행의 단위라고 볼 수 있다. 너무 많은 체이닝으로 명령을 하는 것은 캐시를 무효화(breaking)할 수 있기 때문에 조심해야 하지만, 너무 많이 있는 것은 불필요하다. 패키지를 패키지 매니저에서 설치할 때는, 너는 항상 같은 RUN 인스트럭션에서 인덱스를 업데이트하고 그리고 패키지를 설치하고 싶어한다: 그것들은 함께 하나의 캐싱 가능한 유닛을 형성한다. 이 방법을 사용하지 않으면, 너는 outdated된 패키지를 설치할 위험이 있다.

Reduce image size

작은 이미지들은 빠른 배포와 더 작은 공격 표면(attack surface?)과 이어지기 때문에, 이미지 사이즈는 중요하다.

Tip #4: 불필요한 의존성들을 제거해라

불필요한 의존성을 제거하고 필요한 디버깅 툴이 나중에 설치될 수도 있는 것이면, 디버깅 툴을 설치하지 말아라. 특별한 패키지 매니저(apt와 같은)는 자동적으로 user-specified 패키지들을 불필요한 footprint를 남기면서 설치한다. Apt는 실제로는 불필요한 의존성을 설치하지 않는 --no-install-recommends 플래그가 있다. 그것(해당 옵션으로 인해 설치되지 않은)들이 필요한 경우 명시적으로 그것(필요한 의존성)을 더해라

Tip #5: 패키지 매니저의 캐시를 삭제해라

패키지 매니저들은 그들 스스로의 캐시를 보관한다. 그것들은 결국엔 이미지가 된다. 이것을 해결하는 방법은 패키지를 설치한 같은 RUN 인스트럭션에서 캐시를 삭제하는 것이다. 이것을 다른 RUN 인스트럭션에서 지우게 되면, 이미지 사이즈를 줄이지 못한다.

멀티 스테이지 빌드에서 이미지 사이즈를 줄이는 몇 가지 방법들을 마지막에 다룰 것이다. 다음 베스트 프랙티스는 유지성(maintainability), 보안성, 반복 가능성(repeatability)를 어떻게 최적화 할지에 대해서 다룬다.

Maintainability

Tip #6: 가능한 공식 이미지들을 사용해라

공식 이미지들은 유지에 많은 시간 소비를 하는 것을 막아준다. 모든 인스톨 과정이 완료 되어있고, 베스트 프랙티스가 적용되어 있기 때문이다. 다양한 프로젝트들이 있다면, 그들이 정확히 같은 베이스 이미지를 사용하기 때문에 그들은 그들의 레이어들을 공유할 수 있다.

Tip #7: 더 구체적인 태그들을 사용해라

latest 태그를 사용하지 말아라. 이것은 도커 허브에 있는 공식 이미지들에게 항상 사용가능한 편리한 방법이지만, 가끔은 큰 변화들이 있을 수 있다. 얼마나 오랜 시간을 해당 베이스 이미지를 가지고 개발 했는지에 따라서 도커파일을 캐시 없이 다시 빌드 시켰을 때, 빌드 되지 않을 수 있다.

대신 더 구체적인 태그들을 사용해라. 위 경우는 openjdk를 사용하고 있다. 더 많은 태그들을 사용할 수 있으니 도커 허브 문서를 확인해라

Tip #8: 최소한의 flavors(지원기능)를 찾아라

이 태그들 중 어떤 것들은 최소한의 flavors를 가지고 있는데, 그럴수록 이미지는 더 작다. jre-slim 버전은 최소한의 데비안 이미지를 베이스로 하는데, 더 작은 jre-alpine 버전은 알파인 리눅스 배포판을 기반으로 만들어져 있다. 눈에 띄는 차이점은 데비안은 GNU libc를 사용하는 반면, 알파인은 더 작지만, 몇 가지 성능 이슈들을 야기할 수 있는 mucl libc를 사용한다. 이번 케이스인 openjdk의 경우, (아마 알파인이) sdk가 아니라 java 런타임만 담고 있기 때문에 이것은 극적으로 이미지를 줄일 수 있게 만들어준다.

Reproducibility

지금까지 도커파일들은 너의 jar artifact가 호스트에서 빌드된다고 가정하고 있었다. 이건 너가 컨테이너가 제공해주는 일관적인 환경의 혜택을 잃는다는 점에서 이상적이지는 않다. 예를 들어서 만약 너의 자바 앱이 특정 라이브러리들에 의존하고 있다면, 그것은 아마 어떤 컴퓨터에서 앱이 빌드되는지에 따른 일관적이지 않게 되는 것들을 환영하지 않을 것이다.

Tip #9: 일관적인 환경에서 소스를 빌드해라

소스 코드는 너가 빌드하고자 하는 도커 이미지의 source of truth이다. 그 도커파일은 간단히 말해 청사진이다.

너는 너의 앱을 빌드하기 위해 필요한 모든 것들을 일원화 하는 것으로서 시작할 필요가 있다. 우리의 간단한 자바 앱은 Maven과 JDK를 필요로 한다. 그러므로 우리의 도커파일을 구체적이고 최소화된 공식 maven 이미지 기반으로 하도록 하자. 만약 너가 더 많은 의존성을 설치해야 한다면, RUN 인스트럭션을 통해 추가할 수 있다.

pom.xml과 소스 폴더들은 마지막 mvn package와 함께 app.jar을 생성하는 RUN 단계에서 필요하기 때문에 복사된다. (-e 플래그는 에러를 보여주기 위한 것이고, -Bbatch 모드로 동작시키기 위한 것이다.)

우리는 비일관적인 환경 문제를 해결했다. 하지만 다른 한 가지 방법이 있다: 코드가 바뀔 때마다, 모든 pom.xml에 정의된 모든 의존성들이 받아와진다. 다음 팁에서 고쳐보자.

Tip #10: 의존성을 각 분리된 단계에서 가져오자

위에서 언급했던 ‘캐시 가능한 실행의 단위’들이라는 것을 다시 생각해봄으로써, 우리는 의존성을 받아오는 것이 소스 코드에 의존적인 것이 아니라 pom.xml이 변경되는 것에만 의존하는 분리된 캐시 가능한 유닛이라는 것을 확신할 수가 있다. 두 COPY 단계 사이에 RUN 단계에서 Maven에게 의존성을 가져오라고 지시한다.

일관된 환경속에서 빌딩하는 것에 대해 소개된 내용 중에 한 가지 문제가 더 있다: 이미지가 런타임에는 필요하지 않은 빌드 타임의 모든 의존성을 포함하고 있기 때문에, 우리 이미지가 이전보다 더 커진다.

Tip #11: 빌드 의존성을 제거하기 위해 멀티 스테이지 빌드를 사용해라 (최종 버전)

멀티 스테이지 빌드는 여러 개의 FROM 인스트럭션에서 확인할 수 있다. 각 FROM은 새로운 스테이지를 시작한다. 그들은 AS라는 키워드(위 이미지에서 우리 첫 FROM 스테이지를 나중의 참조를 위해 builder라고 이름 붙인)로 이름 붙여질 수 있다.

두 번째 스테이지는 최종 이미지를 내보낼 우리의 최종적 스테이지이다. 이것은 제한적으로 런타임에 필요로 하는 것을 담을 것이다. 이 경우에는 최소한의 알파인 베이스의 JRE를 담고 있다. 중간적인 빌드 스테이지는 캐시될 것이지만, 최종 이미지에 제공되지는 않는다. artifacts를 최종 이미지에 넣기 위해서 COPY --from=STAGE_NAME을 사용한다. 위 이미지에서는 builder가 되겠다.

멀티 스테이지 빌드는 빌드 타임의 의존성을 제거하는 해결책이 된다.

우리는 빵빵하고, 일관적이지 않게 빌드 하는 것에서 최소한의 이미지를 일관적인 환경에서, 캐시 친화적으로 빌드하는 것으로 왔다. 다음 블로그 포스트에서 우리는 멀티 스테이지 빌드에 대해서 더 자세하게 다룰 예정이다.

후기

정말 좋은 꿀팁들을 많이 얻었다. 초보를 위한 글이라고 도입에서 말한 것처럼 초보에게 정말 많은 도움이 되었다. 글이 순서대로, 쉽게 잘 읽혀서 타입스크립트에서도 바로 적용할 수 있을 것 같다. 도커 블로그 자주 애용해야겠다. 읽고 이해하기가 어렵지 않았는데 한글로 이해하기 쉽게 글을 고치는 것이 너무 익숙하지 않아 글이 훼손된 느낌이다. 여러번 해보다 보면 나아지겠거나 하고 있다.

원본

https://www.docker.com/blog/intro-guide-to-dockerfile-best-practices/?fbclid=IwAR2IvfL95fHkckX1Hq9F0ARfOCbSjjpMgu7nPwNRsf3S8kz1QAvZfrXW3D8

댓글

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×