Info |
---|
2018 년 4 월 21 일 |
Containerizing interpreted languages
Ruby, Python, Node.js, PHP 등의 해석 언어는 코드를 실행하는 인터프리터를 통해 소스 코드를 보냅니다. 이렇게하면 컴파일 단계를 건너 뛸 수있는 이점이 있지만 코드와 함께 인터프리터를 제공해야한다는 단점이 있습니다.
다행히 이러한 언어의 대부분은 훨씬 작은 컨테이너를 실행할 수있는 경량 환경을 포함하는 사전 빌드 된 Docker 컨테이너를 제공합니다.
Node.js 애플리케이션을 가져와 컨테이너화 해 보겠습니다 . 먼저 "node : onbuild"Docker 이미지를 기본으로 사용하겠습니다. Docker 컨테이너의 "onbuild"버전은 실행에 필요한 모든 것을 미리 패키지화하므로 작업을 수행하기 위해 많은 구성을 수행 할 필요가 없습니다. 이것은 Dockerfile이 매우 간단하다는 것을 의미합니다 (단 두 줄!). 하지만 디스크 크기 (약 700MB)를 지불하면됩니다!
Code Block |
---|
FROM node:onbuild
EXPOSE 8080 |
Alpine과 같은 더 작은 기본 이미지를 사용하면 컨테이너의 크기를 크게 줄일 수 있습니다. Alpine Linux는 컨테이너를 작게 유지하면서 많은 앱과 호환되기 때문에 Docker 사용자에게 매우 인기있는 작고 가벼운 Linux 배포판입니다.
운 좋게도 Node.js (및 기타 인기있는 언어) 용 공식 Alpine 이미지에는 필요한 모든 것이 있습니다. 기본 "노드"Docker 이미지와 달리 "node : alpine"은 많은 파일과 프로그램을 제거하여 앱을 실행하는 데 충분합니다.
Alpine Linux 기반 Dockerfile은 onbuild 이미지가 다른 방식으로 수행하는 몇 가지 명령을 실행해야하므로 생성하기가 조금 더 복잡합니다.
Code Block |
---|
FROM node:alpine
WORKDIR /app
COPY package.json /app/package.json
RUN npm install --production
COPY server.js /app/server.js
EXPOSE 8080
CMD npm start |
그러나 결과 이미지가 65MB로 훨씬 더 작기 때문에 그만한 가치가 있습니다!
Containerizing compiled languages
Go, C, C ++, Rust, Haskell 등과 같은 컴파일 된 언어는 많은 외부 종속성없이 실행할 수있는 바이너리를 만듭니다. 즉, 컴파일러와 같은 바이너리를 생성하기위한 도구를 제공하지 않고도 바이너리를 미리 빌드하고 프로덕션에 제공 할 수 있습니다.
Docker의 다단계 빌드 지원을 통해 바이너리와 최소한의 스캐 폴딩 만 쉽게 제공 할 수 있습니다. 방법을 배우자.
Go 애플리케이션 을 가져와이 패턴을 사용하여 컨테이너화 해 보겠습니다 . 먼저 "golang : onbuild"Docker 이미지를 기본으로 사용하겠습니다. 이전과 마찬가지로 Dockerfile은 두 줄에 불과하지만 디스크 크기 측면에서 700MB가 넘는 가격을 지불해야합니다.
Code Block |
---|
FROM golang:onbuild
EXPOSE 8080 |
다음 단계는 더 얇은 기본 이미지 (이 경우 "golang : alpine"이미지)를 사용하는 것입니다. 지금까지 이것은 통역 언어를 위해 우리가 따랐던 것과 같은 과정입니다.
다시 말하지만, Alpine 기본 이미지로 Dockerfile을 만드는 것은 onbuild 이미지가 수행 한 몇 가지 명령을 실행해야하므로 조금 더 복잡합니다.
Code Block |
---|
FROM golang:alpine
WORKDIR /app
ADD . /app
RUN cd /app && go build -o goapp
EXPOSE 8080
ENTRYPOINT ./goapp |
그러나 결과 이미지는 훨씬 작아서 256MB에 불과합니다!
그러나 이미지를 더 작게 만들 수 있습니다. Go와 함께 제공되는 컴파일러 나 기타 빌드 및 디버그 도구가 필요하지 않으므로 최종 컨테이너에서 제거 할 수 있습니다.
golang : alpine 컨테이너에 의해 생성 된 바이너리를 가져와 자체적으로 패키징하기 위해 다단계 빌드를 사용하겠습니다.
Code Block |
---|
FROM golang:alpine AS build-env
WORKDIR /app
ADD . /app
RUN cd /app && go build -o goapp
FROM alpine
RUN apk update && \
apk add ca-certificates && \
update-ca-certificates && \
rm -rf /var/cache/apk/*
WORKDIR /app
COPY --from=build-env /app/goapp /app
EXPOSE 8080
ENTRYPOINT ./goapp |
저것 좀보세요! 이 컨테이너는 크기가 12MB에 불과합니다!
이 컨테이너를 빌드하는 동안 Dockerfile이 컨테이너에 HTTPS 인증서를 수동으로 설치하는 것과 같은 이상한 일을한다는 것을 알 수 있습니다. 기본 Alpine Linux에는 사전 설치가 거의 없기 때문입니다. 따라서 모든 종속성을 수동으로 설치해야하지만 최종 결과는 매우 작은 컨테이너입니다!
참고 : 더 많은 공간을 절약하고 사용하지 않는 종속성을 제거하려면 Google 의 distroless 프로젝트 를 살펴 보는 것이 좋습니다 . Java 사용자는 Jib을 확인할 수도 있습니다 . 그러나 Alpine은 표준 디버깅 도구를 사용하고 종속성을 설치하는 것이 훨씬 쉽기 때문에 기본 이미지로 여전히 좋은 선택입니다 (공격을위한 더 많은 공간과 약간 더 높은 표면적 비용으로).
컨테이너를 만들고 보관할 위치
이미지를 빌드하고 저장하려면 Google Container Builder 와 Google Container Registry를 함께 사용하는 것이 좋습니다 . Container Builder는 매우 빠르며 이미지를 Container Registry에 자동으로 푸시합니다. 대부분의 개발자는 무료 등급에서 모든 작업을 쉽게 수행해야하며 Container Registry는 원시 Google Cloud Storage 와 동일한 가격입니다 (저렴합니다!).
Google Kubernetes Engine 과 같은 플랫폼은 추가 구성없이 Google Container Registry에서 이미지를 안전하게 가져올 수 있으므로 작업이 쉬워집니다.
또한 Container Registry는 즉시 취약성 스캔 도구와 IAM 지원을 제공합니다. 이러한 도구를 사용하면 컨테이너를보다 쉽게 보호하고 잠글 수 있습니다.
더 작은 컨테이너의 성능 평가
사람들은 소형 컨테이너의 가장 큰 장점은 구축 시간과 풀링 시간 모두의 단축이라고 주장합니다. onbuild로 만든 컨테이너와 다단계 프로세스에서 Alpine으로 만든 컨테이너를 사용하여이를 테스트 해 보겠습니다!
요약 : 강력한 컴퓨터 나 Container Builder에 대해서는 큰 차이가 없지만, 더 작은 컴퓨터와 공유 시스템 (많은 CI / CD 시스템처럼)에 대해서는 큰 차이가 있습니다. 작은 이미지는 절대적인 성능 측면에서 항상 더 좋습니다.
대형 머신에서 이미지 빌드
첫 번째 테스트에서는 꽤 튼튼한 노트북을 사용하여 빌드 할 것입니다. 사무실 WiFi를 사용하고 있으므로 다운로드 속도가 매우 빠릅니다!
각 빌드에 대해 캐시에서 모든 Docker 이미지를 제거합니다.
Build:
Info |
---|
|
더 큰 컨테이너의 경우 빌드에 약 10 초가 더 걸립니다. 이 패널티는 초기 빌드에만 지불되지만 지속적 통합 시스템은 모든 빌드에 대해이 가격을 지불 할 수 있습니다.
다음 테스트는 컨테이너를 원격 레지스트리로 푸시하는 것입니다. 이 테스트에서는 Container Registry를 사용하여 이미지를 저장했습니다.
Push:
Info |
---|
|
글쎄, 이것은 흥미로웠다! 12MB 개체와 700MB 개체를 푸시하는 데 동일한 시간이 걸리는 이유는 무엇입니까? Container Registry는 많은 인기있는 기본 이미지에 대한 글로벌 캐시를 포함하여 내부적으로 많은 트릭을 사용하는 것으로 나타났습니다.
마지막으로 레지스트리에서 로컬 컴퓨터로 이미지를 가져 오는 데 걸리는 시간을 테스트하고 싶습니다.
Pull:
Go Onbuild: 26 Seconds
Go Multistage: 6 Seconds
20 초에 두 개의 서로 다른 컨테이너 이미지를 사용하는 것의 가장 큰 차이입니다. 특히 이미지를 자주 가져 오는 경우 더 작은 이미지 사용의 이점을 볼 수 있습니다.
Container Builder를 사용하여 클라우드에서 컨테이너를 빌드 할 수도 있습니다.이 경우 Container Registry에 컨테이너를 자동으로 저장하는 추가 이점이 있습니다.
Build + Push :
Go Onbuild: 25 Seconds
Go Multistage: 20 Seconds
다시 말하지만, 더 작은 이미지를 사용하면 약간의 이점이 있지만 예상했던 것만 큼 극적이지는 않습니다.
소형 기계에 이미지 구축
그렇다면 더 작은 용기를 사용하는 것이 유리할까요? 빠른 인터넷 연결 및 / 또는 Container Builder를 갖춘 강력한 노트북이 있다면 그렇지 않습니다. 그러나 덜 강력한 기계를 사용하면 이야기가 바뀝니다. 이를 시뮬레이션하기 위해 겸손한 Google Compute Engine f1-micro VM을 사용하여 이러한 이미지를 빌드하고, 푸시하고, 가져 왔는데 결과는 놀랍습니다!
Pull:
Go Onbuild: 52 seconds
Go Multistage: 6 seconds
Build:
Go Onbuild: 54 seconds
Go Multistage: 28 seconds
Push:
Go Onbuild: 48 Seconds
Go Multistage: 16 seconds
이 경우 더 작은 컨테이너를 사용하면 정말 도움이됩니다!
Pulling on Kubernetes
컨테이너를 만들고 밀어내는 데 걸리는 시간은 신경 쓰지 않을 수도 있지만 컨테이너를 당기는 데 걸리는 시간은 정말 신경 써야합니다. Kubernetes의 경우 이는 프로덕션 클러스터에서 가장 중요한 지표 일 것입니다.
예를 들어 3 노드 클러스터가 있고 노드 중 하나가 충돌한다고 가정 해 보겠습니다. Kubernetes Engine과 같은 관리 형 시스템을 사용하는 경우 시스템은 그 자리를 대신 할 새 노드를 자동으로 가동합니다.
그러나이 새 노드는 완전히 새롭고 작업을 시작하기 전에 모든 컨테이너를 가져와야합니다. 컨테이너를 가져 오는 데 시간이 오래 걸릴수록 클러스터가 제대로 수행되지 않는 시간이 길어집니다!
이는 클러스터 크기를 늘리거나 (예 : Kubernetes Engine 자동 확장 사용 ) 노드를 새 버전의 Kubernetes로 업그레이드 할 때 발생할 수 있습니다 (이에 대한 향후 에피소드를 위해 계속 지켜봐 주시기 바랍니다).
여러 배포에서 여러 컨테이너의 풀 성능이 여기에 실제로 추가 될 수 있으며 작은 컨테이너를 사용하면 잠재적으로 배포 시간에서 몇 분을 단축 할 수 있음을 알 수 있습니다.
보안 및 취약성
성능 외에도 더 작은 컨테이너를 사용하면 상당한 보안 이점이 있습니다. 작은 컨테이너는 일반적으로 큰 기본 이미지를 사용하는 컨테이너에 비해 공격 표면이 더 작습니다.
몇 달 전에 Go "onbuild"및 "multistage"컨테이너를 구축 했으므로 이후에 발견 된 몇 가지 취약점이 포함되어있을 수 있습니다. Container Registry에 내장 된 취약성 스캔을 사용하면 컨테이너 에서 알려진 취약성을 쉽게 스캔 할 수 있습니다. 우리가 찾은 것을 보자.
와우, 두 사람의 큰 차이입니다! 작은 컨테이너에는 3 개의 "중간"취약성이있는 반면, 더 큰 컨테이너에는 16 개의 중요 취약성과 300 개 이상의 기타 취약성이 있습니다.
드릴 다운하여 더 큰 컨테이너에 어떤 문제가 있는지 살펴 보겠습니다.
대부분의 문제는 우리 앱과 관련이 없으며 오히려 우리가 사용하지 않는 프로그램과 관련이 있음을 알 수 있습니다! 다단계 이미지는 훨씬 더 작은 기본 이미지를 사용하기 때문에 손상 될 수있는 항목이 더 적습니다.
결론
소형 컨테이너 사용의 성능 및 보안 이점은 그 자체로 입증됩니다. 작은 기본 이미지와 "빌더 패턴"을 사용하면 작은 이미지를 더 쉽게 빌드 할 수 있으며 개별 스택 및 프로그래밍 언어를위한 다른 많은 기술도 컨테이너 크기를 최소화 할 수 있습니다. 무엇을 하든지 컨테이너를 작게 유지하려는 노력이 그만한 가치가 있음을 확신 할 수 있습니다!