Okteto 파이프라인 개요, okteto build, pr12er 서버용 Dockerfile 분석
코딩냄비 프로젝트 중
pr12er는 TensorFlow Korea의 논문을 읽고/리뷰하는 모임 PR12에서 촬영된 동영상을 큐레이션하는 프로젝트입니다. 개략적으로 프론트엔드는Flutter, 백엔드는GO로 작성되었으며, 이 둘간의 인터페이스는gRPC/protobuf로 구성되어있습니다. 특히pr12er프로젝트의 백엔드 서버는PR이Merge됨과 동시에Okteto가 제공하는k8s에 배포되는CD루틴을 탑니다.
이 글은 Okteto 에 배포하기위한 파이프라인을 분석하는 총 X 편의 시리즈물 중 첫 번째입니다.
Okteto파이프라인 개요,okteto build,pr12er서버용Dockerfile분석- Okteto에 gRPC용 Deployment, Service, Ingress 설정
- 정적 yaml 파일의 설정을 동적으로 바꾸기
Okteto 파이프라인의 개요
말 그대로 Okteto에 원하는 애플리케이션을 배포하는 일련의 과정(파이프라인)을 정의하는 방법입니다. 일반적으로 okteto-pipeline.yaml 이라는 파일로 작성되며, 그 과정은 다음과 같습니다.
1.애플리케이션에 대한 도커 이미지를 빌드한 후 Okteto 레지스트리에 해당 이미지를 올리는 과정
2.kubectl 명령어로 Deployment, Service 등을 k8s 에 설정
pr12er에 적용된 Okteto 파이프라인 명세서
다음은 pr12er 에서 정의한 okteto-pipeline.yaml이며, 이번 게시글에서는 그 중 첫 번째인 okteto build 명령어를 분석합니다.
| |
// TODO 전체적인 설명 (매우 간단히)
okteto build 명령어
공식문서에 따르면 okteto build 명령어는 다음과 같은 일을 합니다. 이 내용을 가지고, 약간의 옵션 플래그를 더한 pre12er용 okteto-pipeline.yaml의 okteto build 가 하는일을 살펴보죠.
Dockerfile로부터 이미지를 빌드하여Okteto의 레지스트리에 등록
| |
-t 옵션
- 빌드될 이미지명과 태그를 설정합니다. 여기서 설정된 이름으로
Okteto레지스트리에 등록됩니다.
-f 옵션
- 빌드시 사용할
Dockerfile의 위치(path)를 명시합니다.
마지막 server
Dockerfile가 빌드할 디렉터리 대상을 지정합니다.
이를 종합적으로 풀어서 설명하면, server 라는 디렉터리를 참조하여 ./server/deploy/에 위치한 Dockerfile로 okteto.dev/codingpot-pr12er-server:${OKTETO_GIT_COMMIT} 라는 이름의 이미지를 빌드하여 Okteto 레지스트리에 등록하라는 뜻이됩니다.
pr12er 서버용 Dockerfile
그렇다면 Dockerfile은 어떻게 생겼을지도 살펴보겠습니다. 이는 Okteto와는 무관하지만, GO 언어를 사용한 gRPC/protobuf 프로젝트를 위한 기본 골격을 파악하는데 도움이 될 수 있습니다.
| |
우선 FROM이라는 지시자는 새로운 빌드 스테이지를 초기화하며, 이어지는 지시자가 적용될 대상 베이스 이미지로 설정합니다. 상기 Dockerfile에는 FROM 지시자가 두 개 등장하는 데, 이는 빌드 스테이지가 여러개임을 뜻합니다. 첫 번째 FROM의 이미지에서 작업된 내역을 두 번째 FROM으로 전달하여 최종 이미지는 두 번째의것이 되는것이죠. 이 방식은 주로 애플리케이션을 컴파일/빌드하기 위한 여러가지 디펜던시를 담아 애플리케이션의 컴파일/빌드를 수행한 다음, 해당 애플리케이션의 실행 가능한 바이너리 파일만을 경량 이미지에 옮겨 이미지의 사이즈를 최소화하는 데 많이 활용됩니다.
golang:1.16-alpine이미지는alpine이라는 리눅스 배포판에 기초하여GO언어까지가 함께 설치된 것입니다.gcr.io/distroless/static:nonroot는 오로지 구동될 애플리케이션과 런타임 디펜던시만을 포함하고, 기타 일반적인 리눅스에서 제공하는 다양한 패키지(쉘 포함)를 제외한 초경량 이미지입니다. 이에 대한 더 자세한 설명은 게시글 하단의 레퍼런스를 참고하세요.
그렇다면 두 FROM 사이에서 첫 번째 FROM이 해야할 일은 명확합니다. 애플리케이션을 빌드한 바이너리 파일, 그리고 그 애플리케이션을 구동하는 데 필요한 런타임 디펜던시를 구축하는 것이죠. 그렇게 구축된것을 두 번째 FROM에 심어주기 위함입니다.
WORKDIR /src/
- 첫 번째
FROM이미지에서 작업할 디렉터리를 지정합니다.
COPY go.mod ., COPY go.sum .
- 프로젝트에 필요한 모듈 디펜던시와 각 디펜던시에 대한 체크섬이 명시된 두 파일을 현재 작업 디렉터리로 복사합니다.
COPY지시자의 첫 번째 인자는 앞서 지정된server라는 디렉터리를 기준으로 복사될 파일명을 명시하며, 두 번째 인자는 파일이 복사되어 저장될 이미지내 디렉터리를 명시합니다(즉WORKDIR).
RUN go mod download
- 디펜던시에대한 명세서만 있을 뿐, 현재 이미지에는 해당 디펜던시가 없기 때문에, 명세서를 참조하여 모듈을 다운로드 받습니다.
COPY . .
- 디펜던시 외 프로젝트를 구동하는데 필요한 모든 파일을 복사합니다.
RUN ... go build ... -o server cmd/server/main.go
- 이후
go build명령어로cmd/server/main.go라는 파일을 바이너리 파일로 빌드하고, 그 바이너리 파일의 이름을server라고 명명합니다.
이제 구동될 애플리케이션을 빌드하였으니, 실제 애플리케이션 구동에 필요없는 파일들(소스 파일 등)을 제외하고 두 번째 FROM 이미지로 복사합니다. 이 과정은 실제 이미지가 올라갔을 때 최초로 실행되는 지점인 ENTRYPOINT 지시자 이전까지에 해당합니다.
COPY --from=builder /src/server .
--from=builder라는 플래그가 새롭게 추가된COPY지시자 입니다. 앞서 첫 번째FROM지시자에서as builder라고 적은것은 나중에 해당 빌드 스테이지를 참조하기위한 별칭을 지정한 것입니다.- 따라서
builder스테이지에서go build로 만들어진 바이너리 파일인server를 현재 이미지에 복사한다는 뜻이됩니다. 이때/src/server인 이유는 첫 번째 스테이지에서 작업 디렉터리가/src/로 지정되어, 만들어진server바이너리 파일이/src/디렉터리내 위치하기 때문입니다.
COPY --from=builder /bin/grpc_health_probe /bin/grpc_health_probe
grpc_health_probe는gRPC서버의 실행여부를 판단하는 데 쓰이는 유틸리티 프로그램입니다. 이 프로그램은 첫 번째 빌드 스테이지 중RUN지시자에서wget을 통해 다운로드 받은것을 그대로 복사한 것입니다.- 두 번째 스테이지에서 다운로드 받지 못한 이유는 초경량 이미지여서
wget명령어조차 들어있지 않기 때문입니다.
참고자료
- Okteto
- Golang Images
- Distroless Image
- Best practices for building containers
- Distroless Containers: Hype or True Value?