컨테이너 빌드를 위한 권장사항

이 문서에서는 컨테이너를 빌드하기 위한 권장사항을 설명합니다. 컨테이너를 더 쉽게 빌드(예: Cloud Build)하고, Google Kubernetes Engine(GKE)에서 실행하기 쉽도록 빌드 시간 단축부터 이미지를 더 작고 탄력있게 만드는 데 이르기까지 광범위한 목표를 다루고 있습니다.

이러한 권장사항의 중요도는 각기 다릅니다. 예를 들어 권장사항 중 일부를 적용하지 않고 성공적으로 프로덕션 작업 부하를 실행할 수 있더라도 그 외의 권장사항은 필수적일 수 있습니다. 특히 보안 관련 권장사항의 중요도는 주관적입니다. 이러한 권장사항을 구현할지 여부는 각자의 환경과 제약 조건에 따라 달라집니다.

이 문서를 최대한 활용하려면 Docker와 Kubernetes에 대한 어느 정도의 지식이 필요합니다. 여기서 설명하는 일부 권장사항은 Windows 컨테이너에도 적용되지만 대부분은 Linux 컨테이너 작업을 전제로 합니다. 컨테이너의 실행 및 작업에 관한 조언은 컨테이너 작업을 위한 권장사항에서 제공됩니다.

컨테이너당 하나의 앱 패키징

중요도: 높음

컨테이너 작업을 시작할 때 여러 가지 작업을 동시에 실행할 수 있는 가상 머신으로 처리하는 것이 흔한 실수입니다. 컨테이너는 이러한 방식으로 작동할 수 있지만 그렇게 하면 컨테이너 모델의 장점이 대부분 줄어듭니다. 예를 들어 기본 Apache/MySQL/PHP 스택을 선택해 단일 컨테이너에서 모든 구성요소를 실행하려고 할 수 있습니다. 하지만 PHP-FPM을 실행하는 경우 Apache용 컨테이너 하나, MySQL용 컨테이너 하나, PHP용 하나 등과 같이 2개나 3개의 컨테이너를 사용하는 것이 가장 좋습니다.

컨테이너는 호스팅하는 앱과 같은 수명 주기를 사용하도록 설계되었기 때문에 각 컨테이너에는 하나의 앱만 포함되어 있어야 합니다. 컨테이너가 시작될 때 앱이 시작되어야 하고 앱이 중지되면 컨테이너도 중지되어야 합니다. 다음 다이어그램은 이러한 권장사항을 보여줍니다.

커스텀 이미지 없는 부팅 프로세스를 보여주는 다이어그램

그림 1. 왼쪽의 컨테이너가 권장사항을 따릅니다. 오른쪽의 컨테이너는 권장사항을 따르지 않습니다.

하나의 컨테이너에 여러 개의 앱이 있는 경우 수명 주기가 다를 수 있고 다른 상태일 수도 있습니다. 예를 들어 컨테이너는 실행 중이지만 핵심 구성요소 중 하나가 다운되거나 응답하지 않는 상황이 생길 수 있습니다. 추가적인 상태 확인 없이는, 전체 컨테이너 관리 시스템(Docker 또는 Kubernetes)에서 컨테이너가 정상인지 판단할 수 없습니다. Kubernetes의 경우, 필요한 상황에서도 컨테이너가 기본적으로 다시 시작되지 않는다는 의미합니다.

공개 이미지에서 다음과 같은 조치를 보게 될 수도 있지만 그 예시를 따르지는 마세요.

  • supervisord와 같은 프로세스 관리 시스템을 사용하여 컨테이너에 있는 하나 또는 여러 개의 앱을 관리합니다.
  • bash 스크립트를 컨테이너의 진입점으로 사용하고 백그라운드 작업으로 여러 개의 앱을 생성합니다. 컨테이너에서 bash 스크립트를 올바르게 사용하려면 PID 1, 신호 처리, 좀비 프로세스 올바르게 처리하기를 참조하세요.

PID 1, 신호 처리, 좀비 프로세스 올바르게 처리하기

중요도: 높음

Linux 신호는 컨테이너 내부의 프로세스 수명 주기를 제어하는 주요 방법입니다. 기존의 권장사항에 따라 앱의 수명 주기를 컨테이너와 긴밀하게 연결하려면 앱에서 Linux 신호를 올바르게 처리하도록 해야 합니다. 가장 중요한 Linux 신호는 프로세스를 종료하는 SIGTERM입니다. 앱은 비정상적으로 프로세스를 종료하는 데 사용되는 SIGKILL 신호 또는 Ctrl+C를 입력할 때 전송되는 SIGINT 신호를 받을 수 있으며 대개 SIGTERM처럼 처리됩니다.

프로세스 식별자(PID)는 Linux 커널이 각 프로세스에 제공하는 고유한 식별자입니다. PID는 네임스페이스입니다. 즉, 컨테이너에는 호스트 시스템의 PID에 매핑되는 고유한 PID 세트가 있습니다. Linux 커널을 시작할 때 실행된 첫 번째 프로세스에는 PID 1이 있습니다. 정상적인 운영체제의 경우 이 프로세스는 init 시스템(예: systemd 또는 SysV)입니다. 마찬가지로, 컨테이너에서 실행된 첫 번째 프로세스는 PID 1을 얻습니다. Docker와 Kubernetes는 신호를 사용하여 컨테이너 내부의 프로세스와 통신하며, 특히 컨테이너를 종료하기 위해 사용됩니다. Docker와 Kubernetes는 모두 컨테이너 내부에 PID 1이 있는 프로세스에만 신호를 보낼 수 있습니다.

컨테이너의 측면에서 PID와 Linux 신호는 2가지 문제를 제기합니다.

문제 1: Linux 커널이 신호를 처리하는 방법

Linux 커널이 신호를 처리하는 방식은 PID 1을 가진 프로세스와 그렇지 않은 프로세스 간에 차이가 있습니다. 신호 핸들러는 프로세스에 자동으로 등록되지 않으므로 SIGTERM이나 SIGINT와 같은 신호는 기본적으로 아무런 영향을 주지 않습니다. 기본적으로 SIGKILL로 프로세스를 강제 종료하여 모든 정상 종료를 방지해야 합니다. 앱에 따라 SIGKILL을 사용하면 모니터링 시스템에 사용자 표시 오류, 쓰기 중단(데이터 저장용), 원치 않는 알림이 발생할 수 있습니다.

문제 2: 기본 init 시스템이 분리된 프로세스를 처리하는 방법

systemd와 같은 기본 init 시스템은 분리된 좀비 프로세스를 제거(거둘 때)하는 데에도 사용됩니다. 분리된 프로세스(상위 요소가 사라진 프로세스)는 PID 1이 있는 프로세스에 다시 첨부됩니다. PID 1은 프로세스가 사라질 때 다시 거둬야 합니다. 정상적인 init 시스템은 그렇게 작동합니다. 그러나 컨테이너에서는 PID 1을 갖고 있는 프로세스가 이러한 책임을 갖게 됩니다. 해당 프로세스가 올바르게 처리하지 못하면 메모리나 다른 리소스가 부족해질 수 있습니다.

이러한 문제는 몇 가지 일반적인 솔루션이 있으며 다음 섹션에서 개략적으로 설명합니다.

솔루션 1: PID 1으로 실행하고 신호 핸들러로 등록

이 솔루션은 첫 번째 문제만 해결합니다. 앱이 제어된 방식(흔한 경우)으로 하위 프로세스를 생성하면 두 번째 문제를 방지할 수 있습니다.

이 솔루션을 구현하는 가장 쉬운 방법은 Dockerfile에서 CMD 또는 ENTRYPOINT 안내를 사용하여 프로세스를 실행하는 것입니다. 예를 들어 다음 Dockerfile에서 nginx는 실행할 수 있는 최초이자 유일한 프로세스입니다.

FROM debian:9

RUN apt-get update && \
    apt-get install -y nginx

EXPOSE 80

CMD [ "nginx", "-g", "daemon off;" ]

때로는 프로세스가 제대로 실행될 수 있도록 컨테이너에서 환경을 준비해야 할 수 있습니다. 이 경우 컨테이너를 시작할 때 셸 스크립트를 실행하는 것이 가장 좋습니다. 이 셸 스크립트는 환경을 준비하고 기본 프로세스를 실행하는 작업을 담당합니다. 하지만 이 방법을 사용하는 경우 셸 스크립트는 프로세스가 아닌 PID 1을 가지므로 기본 exec 명령어를 사용하여 셸 스크립트에서 프로세스를 실행해야 합니다. exec 명령어로 스크립트를 원하는 프로그램으로 바꿉니다. 그 다음 프로세스에서 PID 1을 상속합니다.

솔루션 2: 특수한 init 시스템 사용

기본적인 Linux 환경에서와 마찬가지로 init 시스템을 사용하여 이러한 문제를 처리할 수도 있습니다. 하지만 systemd 또는 SysV 등의 일반 init 시스템은 단지 이 용도로 사용하기에는 너무 복잡하고 크기 때문에 컨테이너용으로 특별히 제작된 tini 같은 init 시스템을 사용하는 것이 좋습니다.

특수한 init 시스템을 사용하는 경우 init 프로세스는 PID 1을 가지며 다음을 수행합니다.

  • 올바른 신호 핸들러를 등록합니다.
  • 앱에서 신호가 작동하는지 확인합니다.
  • 최종 모든 좀비 프로세스가 거둬집니다.

docker run 명령어의 --init 옵션을 사용하여 Docker 자체에서 이 솔루션을 사용할 수 있습니다. Kubernetes에서 이 솔루션을 사용하려면 컨테이너 이미지에 init 시스템을 설치하고 이를 컨테이너의 진입점으로 사용해야 합니다.

Docker 빌드 캐시 최적화

중요도: 높음

Docker 빌드 캐시는 컨테이너 이미지 빌드를 크게 가속화할 수 있습니다. 이미지는 레이어별로 빌드되며 Dockerfile에서는 각 명령이 결과 이미지에 레이어를 만듭니다. 가능하면 빌드 중에 Docker는 이전 빌드의 레이어를 다시 사용하고 잠재적으로 비용이 많이 드는 단계를 건너뜁니다. Docker는 모든 이전 빌드 단계에서 사용된 경우에만 빌드 캐시를 사용할 수 있습니다. 이 동작은 일반적으로 빌드가 빨라지는 장점이 있지만 몇 가지 경우를 고려해야 합니다.

예를 들어 Docker 빌드 캐시를 최대한 활용하려면 Dockerfile 맨 아래에 자주 변경되는 빌드 단계를 배치해야 합니다. Docker를 맨 위에 배치하면 Docker는 자주 변경되지 않는 다른 빌드 단계에 대해 빌드 캐시를 사용할 수 없습니다. 새 Docker 이미지는 대개 새 버전의 소스 코드마다 작성되므로 Dockerfile에서 소스 코드를 가능한 한 늦게 이미지에 추가합니다. 다음 다이어그램에서 STEP 1을 변경하면 Docker는 FROM debian:9 단계의 레이어만 다시 사용한다는 것을 알 수 있습니다. 하지만 STEP 3를 변경하면 Docker는 STEP 1 STEP 2의 레이어를 다시 사용할 수 있습니다.

Docker 빌드 캐시를 사용하는 방법의 예

그림 2. Docker 빌드 캐시를 사용하는 방법의 예. 초록색은 다시 사용할 수 있는 레이어입니다. 빨간색에 있는 레이어는 다시 만들어야 합니다.

레이어 재사용은 또 다른 결과를 초래합니다. 빌드 단계가 로컬 파일 시스템에 저장된 모든 종류의 캐시에 의존하는 경우 이 캐시는 동일한 빌드 단계에서 생성되어야 합니다. 이 캐시가 생성되지 않으면 빌드 단계가 이전 빌드에서 나온 오래된 캐시로 실행될 수 있습니다. apt 또는 yum:과 같은 패키지 관리자에서 가장 일반적으로 이 동작을 볼 수 있습니다. 패키지 설치와 동일한 RUN 명령어로 저장소를 업데이트해야 합니다.

다음 Dockerfile에서 두 번째 RUN 단계를 변경하면 apt-get update 명령어가 다시 실행되지 않고 이전의 apt 캐시로 남습니다.

FROM debian:9

RUN apt-get update
RUN apt-get install -y nginx

그 대신, 하나의 RUN 단계에서 2개의 명령을 병합하세요.

FROM debian:9

RUN apt-get update && \
    apt-get install -y nginx

불필요한 도구 제거

중요도: 보통

앱을 공격자로부터 보호하려면 불필요한 도구를 제거하여 앱의 공격 영역을 줄이도록 합니다. 예를 들어 netcat과 같은 유틸리티를 삭제하면 시스템 내부에 역전 셸을 만들 수 있습니다. netcat이 컨테이너에 없으면 공격자는 다른 방법을 찾아야 합니다.

이 권장사항은 컨테이너화되지 않은 경우에도 모든 작업 부하에 적용됩니다. 차이점은 기본 가상 머신이나 베어 메탈 서버보다 컨테이너로 구현하는 것이 훨씬 간단하다는 점입니다.

이러한 도구 중 일부는 디버깅에 유용할 수 있습니다. 예를 들어 이 권장사항을 제대로 적용한다면 철저한 로그, 프로파일링, 애플리케이션 성능 관리 시스템이 필수적인 부분이 됩니다. 결과적으로 로컬 디버깅 도구가 더 높은 권한을 부여 받기 때문에 더 이상 로컬 디버깅 도구에 의존할 수 없습니다.

파일 시스템 콘텐츠

이 권장사항의 첫 번째 파트는 컨테이너 이미지의 콘텐츠를 다룹니다. 이미지에 콘텐츠를 가능한 한 적게 유지하세요. 하나의 정적으로 연결된 바이너리로 앱을 컴파일해 스크래치 이미지로 이 바이너리를 추가하면 오로지 사용자의 앱만 포함된 최종 이미지를 얻을 수 있습니다. 이미지에 패키징된 도구의 수를 줄임으로써 잠재적 공격자가 컨테이너에서 수행할 수 있는 작업을 줄일 수 있습니다. 자세한 내용은 최소 크기의 이미지 빌드하기를 참조하세요.

파일 시스템 보안

이미지에 도구를 포함하지 않는 것만으로는 충분하지 않습니다. 잠재적 공격자가 자신의 도구를 설치하는 것도 막아야 합니다. 여기서는 2가지 방법을 함께 사용할 수 있습니다.

  • 컨테이너 내부에서 루트로 실행하지 않기: 이 방법은 보안의 첫 번째 레이어를 제공하며 예를 들어 공격자가 이미지(apt-get 또는 apk)에 포함된 패키지 관리자를 사용하여 루트 소유 파일을 수정하지 못하게 할 수 있습니다. 이 방법이 효과를 보려면 sudo 명령을 사용 중지하거나 제거해야 합니다. 이 주제는 루트로 실행하지 않기에서 더 자세히 설명합니다.

  • docker run 명령어에서 --read-only 플래그를 사용하거나 Kubernetes에서 readOnlyRootFilesystem 옵션을 사용하여 수행하는 읽기 전용 모드로 컨테이너를 실행합니다. PodSecurityPolicy를 사용하여 Kubernetes에서 이를 시행할 수 있습니다.

최소 크기의 이미지 빌드하기

중요도: 보통

작은 이미지를 빌드하면 업로드 및 다운로드 시간이 더 단축되는 등의 이점이 있으며, 이는 Kubernetes에서 포드의 콜드 스타트 시간에 특히 중요합니다. 이미지가 작을수록 노드를 다운로드하는 속도가 빨라집니다. 그러나 실수로 빌드 종속 항목이나 최종 이미지에 최적화되지 않은 레이어를 포함하기 쉽기 때문에 작은 이미지를 빌드하는 것이 어려울 수 있습니다.

최소 크기의 기본 이미지 사용

기본 이미지는 Dockerfile의 FROM 명령에서 참조되는 이미지입니다. Dockerfile의 다른 모든 명령은 이 이미지 위에 빌드됩니다. 기본 이미지가 작을수록 결과 이미지가 작아지고 다운로드 속도는 빨라집니다. 예를 들어 alpine:3.7 이미지는 centos:7 이미지보다 71MB 더 작습니다.

자신만의 런타임 환경을 구축할 수 있는 빈 이미지인 스크래치 기본 이미지를 사용할 수도 있습니다. 앱이 정적으로 링크된 바이너리인 경우 스크래치 기본 이미지를 쉽게 사용할 수 있습니다.

FROM scratch
COPY mybinary /mybinary
CMD [ "/mybinary" ]

distroless 프로젝트는 여러 언어에 최소한의 기본 이미지를 제공합니다. 이미지에는 언어에 대한 런타임 종속 항목만 포함되며, Linux 배포판에서 기대할 수 있는 많은 도구(셸, 패키지 관리자 등)는 포함되지 않습니다.

이미지의 혼잡성 줄이기

이미지의 크기를 줄이려면 이미지 내부에 꼭 필요한 부분만 설치하세요. 추가 패키지를 설치했다가 나중에 패키지를 제거하고 싶을 수도 있습니다. 그러나 이러한 접근법은 충분하지 않습니다. Dockerfile의 각 명령은 레이어를 생성하므로 이미지를 만든 단계보다 나중 단계에서 이미지의 데이터를 제거해도 전체 이미지의 크기는 줄어들지 않습니다. 데이터는 그대로 있으며 더 깊은 레이어에 숨겨질 뿐입니다. 다음 예제를 참조하세요.

잘못된 Dockerfile 올바른 Dockerfile

FROM debian:9
RUN apt-get update && \ apt-get install -y \ [buildpackage] RUN [build my app] RUN apt-get autoremove --purge \ -y [buildpackage] && \ apt-get -y clean && \ rm -rf /var/lib/apt/lists/*

FROM debian:9
RUN apt-get update && \ apt-get install -y \ [buildpackage] && \ [build my app] && \ apt-get autoremove --purge \ -y [buildpackage] && \ apt-get -y clean && \ rm -rf /var/lib/apt/lists/*

Dockerfile의 잘못된 버전에서 [buildpackage]/var/lib/apt/lists/*의 파일이 첫 번째 RUN 파일에 해당하는 레이어에 여전히 존재합니다. 이 레이어는 이미지의 일부이므로 결과 이미지에서 이미지에 포함된 데이터에 액세스할 수 없는 경우에도 나머지는 업로드하고 다운로드해야 합니다.

Dockerfile의 올바른 버전에서는 모든 기능이 기본 제공 앱만 포함된 단일 레이어에서 수행됩니다. [buildpackage]/var/lib/apt/lists/*의 파일은 결과 이미지의 어느 곳에도 존재하지 않으며 더 깊은 레이어에도 숨겨지지 않습니다.

이미지 레이어에 대한 자세한 내용은 Docker 빌드 캐시 최적화를 참조하세요.

이미지의 혼잡성을 줄이는 또 다른 좋은 방법은 다중 단계 빌드(Docker 17.05에서 소개됨)를 사용하는 것입니다. 다중 단계 빌드를 사용하면 첫 번째 '빌드' 컨테이너에서 앱을 빌드하고 동일한 Dockerfile을 사용하면서 다른 컨테이너에서 결과를 사용할 수 있습니다.

Docker 다중 단계 빌드 프로세스

그림 3. Docker 다중 단계 빌드 프로세스

다음 Dockerfile에서 hello 바이너리는 첫 번째 컨테이너에 빌드되어 두 번째 컨테이너에 삽입됩니다. 두 번째 컨테이너는 스크래치를 기반으로 하기 때문에, 결과 이미지에는 hello 바이너리만 포함되고 빌드하는 동안 필요한 소스 파일과 객체 파일은 포함되지 않습니다. 스크래치 이미지에서 외부 라이브러리의 필요 없이 작동하려면 바이너리를 정적으로 링크해야 합니다.

FROM golang:1.10 as builder

WORKDIR /tmp/go
COPY hello.go ./
RUN CGO_ENABLED=0 go build -a -ldflags '-s' -o hello

FROM scratch
CMD [ "/hello" ]
COPY --from=builder /tmp/go/hello /hello

공통 레이어로 이미지 만들어보기

Docker 이미지를 다운로드해야 하는 경우 Docker는 먼저 이미지에 있는 일부 레이어가 이미 있는지 확인합니다. 해당 레이어가 있는 경우 레이어는 다운로드되지 않습니다. 이 상황은 현재 다운로드 중인 이미지와 같은 기본 이미지를 이전에 다운로드한 경우 발생할 수 있습니다. 결과적으로 두 번째 이미지에서는 다운로드되는 데이터의 양이 훨씬 적습니다.

조직 수준에서 개발자에게 일련의 공통적인 표준 기본 이미지를 제공하여 이러한 데이터 감소를 활용할 수 있습니다. 시스템은 각 기본 이미지를 한 번만 다운로드해야 합니다. 처음 다운로드한 후에는 각 이미지를 고유하게 만드는 레이어만 필요합니다. 결과적으로 공통된 이미지가 많을수록 다운로드 속도가 빨라집니다.

공통 레이어로 이미지 만들어보기

그림 4. 공통 레이어로 이미지 만들기

Container Registry에서 취약점 스캔 사용

중요도: 보통

소프트웨어 취약점은 베어 메탈 서버 및 가상 머신의 세계에서 잘 알려진 문제입니다. 이러한 취약점을 해결하는 일반적인 방법은 각 서버에 설치된 패키지를 나열하는 중앙 집중식 인벤토리 시스템을 사용하는 것입니다. 업스트림 운영체제의 취약점 피드를 구독하여 취약점이 서버에 영향을 미치면 알림을 받고 이에 따라 패치하세요.

하지만 컨테이너는 변경될 수 없기 때문에 (자세한 내용은 컨테이너의 상태 비추적 및 불변성 참조) 취약점이 있는 경우 컨테이너를 패치하지 마세요. 가장 좋은 방법은 포함된 이미지와 패치를 다시 빌드하고 다시 배포하는 것입니다. 컨테이너는 수명 주기가 짧으며 서버에 비해 잘 정의된 ID가 없습니다. 따라서 비슷한 중앙 집중식 인벤토리 시스템을 사용하는 방식은 컨테이너의 취약점을 감지하기에 좋지 않습니다.

이 문제를 해결하는 데 도움이 되도록 Container Registry에는 취약점 스캔 기능이 있습니다. 이 기능을 사용하면 컨테이너 이미지의 패키지 취약점을 식별합니다. 이미지가 Container Registry에 업로드될 때마다, 그리고 취약점 데이터베이스에 대한 업데이트가 있을 때마다 이미지를 스캔합니다. 이 기능으로 보고된 정보는 다음과 같은 여러 방법으로 처리할 수 있습니다.

  • 크론과 유사한 작업을 만들어 취약점을 나열하고, 수정 방법이 존재하는 경우 프로세스를 트리거하여 수정합니다
  • 취약점이 감지되자마자 Cloud Pub/Sub 통합을 사용하여 조직에서 사용하는 패치 프로세스를 트리거합니다.

패치 프로세스를 자동화하고 이미지를 빌드하는 데 처음 사용된 기존의 지속적 통합 파이프라인을 사용하는 것이 좋습니다. 지속적 배포 파이프라인을 잘 알고 있다면 준비가 완료되면 고정 이미지를 자동으로 배포할 수도 있습니다. 하지만 대부분의 사람들은 배포 전에 수동 확인 단계를 선호합니다. 아래의 프로세스를 통해 다음을 달성할 수 있습니다.

  1. Container Registry에 이미지를 저장하고 취약점 스캔을 사용 설정합니다.
  2. Container Registry에서 새로운 취약점을 정기적으로 가져오는 작업을 설정하고 필요한 경우 이미지 다시 빌드를 트리거합니다.
  3. 새 이미지가 빌드되면 지속적 배포 시스템에서 스테이징 환경에 해당 이미지를 배포합니다.
  4. 문제의 스테이징 환경을 수동으로 확인합니다.
  5. 문제가 발견되지 않으면 배포를 프로덕션 환경으로 수동 트리거합니다.

이미지에 올바르게 태그하기

중요도: 보통

Docker 이미지는 일반적으로 이름과 태그라는 2가지 구성요소로 식별됩니다. 예를 들어 google/cloud-sdk:193.0.0 이미지의 경우 'google/cloud-sdk'는 이름이고 '193.0.0'은 태그입니다. Docker 명령어에 태그를 제공하지 않으면 기본적으로 '최신' 태그가 사용됩니다. 이름/태그 쌍은 시간마다 다르게 지정됩니다. 그러나 필요에 따라 태그를 다른 이미지에 재할당할 수 있습니다.

이미지를 빌드할 때는 올바르게 태그를 해야 합니다. 일관되고 논리적인 태그 정책을 따르세요. 이미지 사용자가 쉽게 이해할 수 있도록 태그 정책을 문서화하세요.

컨테이너 이미지는 하나의 소프트웨어를 패키징하고 출시하는 방법입니다. 이미지를 태그하면 사용자가 소프트웨어의 특정 버전을 식별하여 다운로드할 수 있습니다. 따라서 컨테이너 이미지의 태그 시스템과 소프트웨어의 출시 정책을 긴밀하게 연결해야 합니다.

의미 체계 버전 관리를 사용하여 태그하기

소프트웨어를 출시하는 일반적인 방법은 버전 번호가 있는 소스 코드의 특정 버전을 '태그'(예: git tag 명령어)하는 것입니다. 의미 체계 버전 관리 지정을 사용하면 버전 번호를 깔끔하게 처리할 수 있습니다. 이 시스템에서 소프트웨어에는 3부분으로 된 버전 번호인 'X.Y.Z'가 있습니다. 각 항목의 의미는 다음과 같습니다.

  • X는 호환되지 않는 API 변경에 대해서만 증가되는 주 버전입니다.
  • Y는 새로운 기능을 위해 증가되는 부 버전입니다.
  • Z는 버그 수정을 위해 증가되는 패치 버전입니다.

부 버전 또는 패치 버전 번호의 증가는 반드시 역호환되는 변경사항 때문에 발생해야 합니다.

이 시스템 또는 유사한 시스템을 사용하는 경우 다음 정책에 따라 이미지를 태그합니다.

  • '최신' 태그는 항상 최신의 안정적인 이미지를 나타냅니다. 이 태그는 새 이미지가 생성되자마자 이동됩니다.
  • 'X.Y.Z' 태그는 소프트웨어의 특정 버전을 나타냅니다. 다른 이미지로 이동하면 안됩니다.
  • 'X.Y' 태그는 'X.Y' 소프트웨어의 부 버전 브랜치 최신 패치 출시를 나타냅니다. 새 패치 버전이 출시되면 이동됩니다.
  • 'X' 태그는 'X' 주 브랜치의 최신 부 버전의 최신 패치 출시를 나타냅니다. 새 패치 버전 또는 새 부 버전이 출시될 때 이동됩니다.

이 정책을 사용하면 사용자가 사용하려는 소프트웨어 버전을 유연하게 선택할 수 있습니다. 특정 'X.Y.Z' 버전을 선택하고 이미지가 변경되지 않도록 하거나 덜 구체적인 태그를 선택하여 업데이트를 자동으로 받을 수 있습니다.

Git 커밋 해시를 사용하여 태그하기

고급 지속적 배포 시스템을 사용하고 있으며 소프트웨어를 자주 출시하는 경우 의미 체계 버전 관리 지정에 설명된 대로 버전 번호를 사용하지 않을 것입니다. 이 경우 버전 번호를 처리하는 일반적인 방법은 Git 커밋 SHA-1 해시(또는 짧은 버전)를 버전 번호로 사용하는 것입니다. 원래 Git 커밋 해시는 변경이 불가능하며 특정 버전의 소프트웨어를 참조합니다.

이 커밋 해시를 소프트웨어의 버전 번호로 사용할 수 있을 뿐만 아니라 특정 버전의 소프트웨어로 빌드된 Docker 이미지의 태그로 사용할 수도 있습니다. 이렇게 하면 Docker 이미지가 추적 가능하게 됩니다.이 경우 이미지 태그가 변경되지 않기 때문에 특정 컨테이너에서 실행 중인 소프트웨어의 특정 버전을 즉시 알 수 있습니다. 지속적 배포 파이프라인에서 배포에 사용된 버전 번호의 업데이트를 자동화하세요.

공개 이미지 사용 여부를 신중하게 고려하기

중요도: 해당 없음

Docker의 가장 큰 장점 중 하나는 모든 종류의 소프트웨어에 대해 공개적으로 사용할 수 있는 이미지의 수가 많다는 것입니다. 이러한 이미지를 사용하여 빠르게 시작할 수 있습니다. 하지만 조직의 컨테이너 전략을 설계할 때 공개적으로 제공되는 이미지로는 충족하기 힘든 제약조건이 있을 수 있습니다. 공개 이미지를 사용할 수 없게 만드는 제약조건의 예시는 다음과 같습니다.

  • 이미지 내부의 콘텐츠를 정확하게 제어하려 합니다.
  • 외부 저장소에 의존하고 싶지 않습니다.
  • 프로덕션 환경의 취약점을 엄격하게 제어하려 합니다.
  • 모든 이미지에 동일한 기본 운영체제가 필요합니다.

모든 제약조건에 대한 답은 하나이며, 비용이 많이 듭니다. 바로 사용자가 직접 이미지를 빌드하는 것입니다. 이미지 수가 제한적일 때는 직접 이미지를 빌드하는 것이 가능하지만 이미지의 수는 빠르게 증가하기 마련입니다. 이러한 시스템을 대규모로 관리하고 싶으면 다음을 고려해 보세요.

  • 드물게 빌드하는 이미지의 경우에도 안정적인 방식으로 이미지를 빌드할 수 있는 자동화된 방법. 이를 위해 Cloud Build에서 트리거를 빌드하면 좋습니다.
  • 표준화된 기본 이미지. Google은 사용 가능한 몇 가지 기본 이미지를 제공합니다.
  • 기본 이미지의 업데이트를 '하위' 이미지로 전파하는 자동화 방법
  • 이미지에서 취약점을 처리하는 방법. 자세한 내용은 Container Registry에서 취약점 분석 사용을 참조하세요.
  • 조직의 여러 팀에서 만든 이미지에 내부 표준을 적용하는 방법

빌드하고 배포하는 이미지에 정책을 적용하는 데 도움이 되는 몇 가지 도구가 있습니다.

  • container-diff는 이미지의 콘텐츠를 분석할 수 있으며 두 이미지를 비교할 수도 있습니다.
  • container-structure-test는 이미지의 콘텐츠가 정의된 규칙 세트를 준수하는지 테스트할 수 있습니다.
  • Grafeas는 이미지에 대한 메타데이터를 저장하여 나중에 해당 이미지가 정책을 준수하는지 확인하는 아티팩트 메타데이터 API입니다.
  • Kubernetes에는 Kubernetes에서 작업 부하를 배포하기 전에 몇 가지 필수 조건을 확인하는 데 사용할 수 있는 허용 컨트롤러가 있습니다.
  • Kubernetes에는 또한 클러스터에서 보안 옵션 사용을 강화하는 데 사용할 수 있는 포드 보안 정책이 있습니다.

하이브리드 시스템을 채택하는 것도 좋습니다. Debian 또는 Alpine 공개 이미지를 기본 이미지로 사용하고 그 이미지 위에 모든 것을 빌드하는 것입니다. 또는 중요하지 않은 이미지의 일부에 공개 이미지를 사용하고 다른 경우에 자신의 이미지를 직접 빌드할 수도 있습니다. 이러한 질문에는 정답이 없지만 해결해야 합니다.

라이선스 참고사항

Docker 이미지에 타사 라이브러리 및 패키지를 포함하기 전에 각 라이선스를 통해 허용되는 부분인지 확인하세요. 타사 라이선스로 인해, Docker 이미지를 공개 레지스트리에 게시할 때 적용되는 재배포 제한사항이 있을 수 있습니다.

다음 단계

다른 Google Cloud Platform 기능을 직접 사용해 보세요. 가이드를 살펴보세요.

이 페이지가 도움이 되었나요? 평가를 부탁드립니다.

다음에 대한 의견 보내기...