무스마 백엔드 살펴보기 - 개발환경 세팅부터 배포까지 (2/2)

들어가면서

분량이 너무 길어져 1편을 작성한 뒤 일주일 안으로 2편을 쓰겠다고 다짐하였던 게으른 작년의 저를 반성하며 뒤늦게 2편을 작성합니다.

1편에서는 프로젝트 생성과 환경 설정, 간단한 기능구현과 함께 실행파일을 생성해보았는데요.

2편에서는 생성한 실행파일을 도커 이미지로 만들고 배포하는 과정을 다뤄보겠습니다.

모든 코드는 GitHub에서 확인하실 수 있습니다.

도커 이미지 생성

이번에는 실행파일(바이너리)을 이용하여 도커 이미지를 생성해 보겠습니다.

먼저 기존에 생성한 실행파일은 macos 플랫폼 위에서 테스트해보기 위해 target 옵션을 macos로 패키징 하였습니다.

그러나 궁극적으로는 리눅스 플랫폼 위에서 실행할 것이기에 target 옵션을 linux로 변경하여야 합니다.

따라서 build:pkg 스크립트를 아래와 같이 변경합니다.

{
  "scripts": { 
    "build:pkg": "pkg . --target node16-linux-x64 --output dist/server",
  }
}

스크립트 변경을 완료하였다면 실행파일을 재생성합니다.

$ yarn build:pkg

도커 파일 생성

아래의 순서로 실행되도록 도커 파일을 생성합니다.

  1. 우분투 설치
  2. 바이너리 파일 복사
  3. 바이너리 실행

[참고] 도커 파일의 이름은 Dockerfile로 생성하여야 합니다.

FROM ubuntu:latest

COPY ./dist/server ./

CMD ["./server" ]

도커 이미지 생성

도커 이미지는 도커 파일을 생성한 뒤 build 명령어로 이미지를 생성할 수 있습니다.

build 명령어를 이용하여 이미지 이름을 backend로 생성하겠습니다.

# [기본형] docker build [옵션] [Dockerfile 경로]

$ docker build -t backend .

수 초가 지나면 backend 이름으로 이미지가 생성됩니다.

생성된 이미지는 images 명령어를 이용하여 확인할 수 있습니다.

$ docker images

REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
backend      latest    80de37027f76   4 seconds ago   127MB

이미지 생성이 완료됐으므로 run 명령어를 이용하여 이미지를 실행한 뒤, GET 요청을 테스트를 해보겠습니다.

# [기본형] docker run -d -p [PORT]:[PORT] [IMAGE_NAME]

$ docker run -d -p 3000:3000 backend
$ curl -XGET 'localhost:3000/get'

{"type":"GET","data":"getResponse"}

도커 이미지 업로드

생성한 도커 이미지를 쿠버네티스에서 사용하기 위해서는 컨테이너 저장소에 이미지를 업로드하여야 하는데요.

현재 컨테이너 저장소로는 대표적으로 세 가지가 있습니다.

  1. AWS ECR(Amazon Elastic Container Registry)
  2. DockerHub
  3. GitHub Container Registry

이번 실습은 3번 GitHub Container Registry 진행하도록 하겠습니다.

그 이유는

  1. 1번, 2번은 과금이 발생할 여지가 있는데 반해 3번은 무료이다.
  2. 코드 저장소인 GitHub에서 서비스를 제공하고 있기에 통합 관리가 수월하다.

GitHub Container Registry 로그인

이미지 업로드를 위하여 컨테이너 레지스트리 서비스에 로그인을 진행하겠습니다.

1편에서 만들어두었던 GitHub Personal access tokens(이하 PAT)을 환경변수로 등록합니다.

# [기본형] export CR_PAT=[PAT]

$ export CR_PAT=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

login 명령어를 이용하여 컨테이너 레지스트리 서비스에 로그인합니다.

# [기본형] echo $CR_PAT | docker login ghcr.io -u [USERNAME] --password-stdin

$ echo $CR_PAT | docker login ghcr.io -u ber01 --password-stdin

Login Succeeded

도커 이미지 재생성

컨테이너 레지스트리에 이미지를 업로드하기 위해서는,

이미지 이름을 ghcr.io/[OWNER]/[IMAGE_NAME]:[TAG] 형태로 이미지를 재생성 해야 합니다.

의미하는 바는 아래와 같습니다.

  • ghcr.io : 컨테이너 레지스트리 패키지 네임스페이스
  • OWNER : GitHub 유저네임 (e.g. ber01)
  • IMAGE_NAME : 이미지 이름 (e.g. backend)
  • TAG : 태그 (e.g. 1.0.0)

tag 명령어와 생성해둔 backend 이미지를 이용하여 이미지를 재생성합니다.

# [기본형] docker tag [IMAGE_ID] ghcr.io/[OWNER]/[IMAGE_NAME]:[TAG]

$ docker tag backend ghcr.io/ber01/backend:1.0.0

images 명령어를 이용하여 생성된 이미지를 확인할 수 있습니다.

$ docker images

REPOSITORY              TAG       IMAGE ID       CREATED          SIZE
backend                 latest    80de37027f76   41 minutes ago   127MB
ghcr.io/ber01/backend   1.0.0     80de37027f76   41 minutes ago   127MB

도커 이미지 푸시

이미지 생성을 완료했으므로 push 명령어를 이용하여 이미지를 푸시해보겠습니다.

# [기본형] docker push ghcr.io/[USERNAME]/[IMAGE_NAME]:[TAG]

$ docker push ghcr.io/ber01/backend:1.0.0

The push refers to repository [ghcr.io/ber01/backend]
1e14209f4d2a: Pushed 
36ffdceb4c77: Pushed 
1.0.0: digest: sha256:2be66ff7847f74d30ebe5e68fe59d764d9562133b4e1169c400b5a284b35a5af size: 740

수 초가 지나면 이미지 푸쉬에 완료되고 GitHub의 packages 탭으로 가면 이미지가 업로드 된 것을 확인할 수 있습니다.

1

2

[참고] 저장소에 패키지 노출시키기

간혹 GitHub를 저장소를 구경하다 보면 저장소의 Packages 탭에 이미지가 노출되어 있는 것을 볼 수가 있습니다.

스크린샷 2022-02-04 오전 10 40 32

이는 도커파일의 LABEL 명령어를 이용하여 노출시킬 수 있는데요.

아래와 같이 기존에 생성해둔 도커파일에 LABEL 명령어를 추가해 보겠습니다.

# [기본형] org.opencontainers.image.source [REPOSITORY_URL]

LABEL org.opencontainers.image.source https://github.com/ber01/techblog-draft

이후 이미지 생성 → 푸시 과정을 수행하게 되면, 저장소에 이미지가 노출된 것을 볼 수 있습니다.

4

도커 이미지 풀

이미지 푸시를 완료하였으므로 이번에는 이미지 풀을 하여 이미지를 가져와보겠습니다.

우선 혼란을 막기 위하여 기존에 생성한 컨테이너와 이미지는 전부 제거하겠습니다.

$ docker rm -f $(docker ps -a -q) # 컨테이너 제거

$ docker rmi -f $(docker images -a) # 이미지 제거

pull 명령어를 이용하여 이미지를 다운로드하겠습니다.

# [기본형] docker pull ghcr.io/[USERNAME]/[IMAGE_NAME]:[TAG]

$ docker pull ghcr.io/ber01/backend:1.0.0

이미지 풀이 완료됐다면 run 명령어를 이용하여 이미지를 실행 후 GET 요청을 테스트해보면 정상적으로 성공하는 것을 볼 수 있습니다.

$ docker run -d -p 3000:3000 ghcr.io/ber01/backend:1.0.0
$ curl -XGET 'localhost:3000/get'

{"type":"GET","data":"getResponse"}

쿠버네티스에서 컨테이너 저장소 이미지 사용하기

쿠버네티스 관련 포스팅이 아니기 때문에 명령어 설명은 생략하겠습니다.

우선 1편에서 다운받은 로컬 쿠버네티스인 minikube를 실행하겠습니다.

minikube start

쿠버네티스 클러스터가 컨테이너 레지스트에 인증하기 위해서는 인증을 위한 시크릿이 필요한데요.

여러 가지 타입의 시크릿 중 kubernetes.io/dockerconfigjson 타입의 시크릿을 생성하여야 합니다.

이를 위해서 우선 인증 토큰을 생성해야 합니다.

인증 토큰 생성

인증 토큰을 생성하기 전에 write:packages 스코프를 선택한 뒤 토큰을 발급받은 것 처럼,

read:packages 스코프를 선택한 뒤 PAT를 발급받습니다.

이후 USERNAMEPAT를 base64로 인코딩하여 AUTH_TOKEN 생성합니다.

# [기본형] echo -n "[USERNAME]:[READ_TOKEN]" | base64

$ echo -n "ber01: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" | base64

YmVyMDE6Z2hwXzNjNUt0U1dIT29CeVFvdWRTbURNcTZtdVVsaEp1eDJuRU5xUA====

시크릿 값 생성

생성한 AUTH_TOKEN을 아래의 형식에 끼워넣습니다.

{
  "auths":{
    "ghcr.io":{
      "auth":"[AUTH_TOKEN]"
    }
  }
}

위 형식을 base64로 인코딩한 뒤 kubernetes.io/dockerconfigjson 타입의 시크릿 값으로 사용합니다.

# [기본형] echo -n '{"auths":{"ghcr.io":{"auth":"[AUTH_TOKEN]"}}}' | base64

$ echo -n '{"auths":{"ghcr.io":{"auth":"YmVyMDE6Z2hwXzNjNUt0U1dIT29CeVFvdWRTbURNcTZtdVVsaEp1eDJuRU5xUA=="}}}' | base64

eyJhdXRocyI6eyJnaGNyLmlvIjp7ImF1dGgiOiJZbVZ5TURFNloyaHdYek5qTlV0MFUxZElUMjlDZVZGdmRXUlRiVVJOY1RadGRWVnNhRXAxZURKdVJVNXhVQT09In19fQ==

시크릿 생성

ghcr-read-secret.yaml이름으로 시크릿 매니페스트를 아래와 같이 생성합니다.

# ghcr-read-secret.yaml

kind: Secret
type: kubernetes.io/dockerconfigjson
apiVersion: v1
metadata:
  name: ghcr-read-secret
data:
  # [기본형] .dockerconfigjson: [SECRET_VALUE]
  .dockerconfigjson: eyJhdXRocyI6eyJnaGNyLmlvIjp7ImF1dGgiOiJZbVZ5TURFNloyaHdYek5qTlV0MFUxZElUMjlDZVZGdmRXUlRiVVJOY1RadGRWVnNhRXAxZURKdVJVNXhVQT09In19fQ==

생성한 시크릿 매니페스트를 클러스터에 적용합니다.

# [기본형] kubectl apply -f [MANIFEST_NAME] --context [CONTEXT]

$ kubectl apply -f ghcr-read-secret.yaml --context minikube

디플로이먼트 생성

이제 모든 준비가 완료되었습니다.

클러스터에 배포를하기 위하여 backend.yaml 이름으로 디플로이먼트 매니페스트를 생성합니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/component: app
      app.kubernetes.io/name: backend
  template:
    metadata:
      labels:
        app.kubernetes.io/component: app
        app.kubernetes.io/name: backend
    spec:
      containers:
        - name: main
          # [기본형] image: ghcr.io/[]/[IMAGE_NAME]:[TAG]        
          image: ghcr.io/ber01/backend:1.0.0
          imagePullPolicy: Always
      imagePullSecrets:
        - name: ghcr-read-secret

생성한 디플로이먼트 매니페스트를 클러스터에 적용합니다.

# [기본형] kubectl apply -f [MANIFEST_NAME] --context [CONTEXT]

$ kubectl apply -f backend.yaml --context minikube

get 명령어를 이용하여 정상적으로 생성 되었는지 확인해보겠습니다.

# [기본형] kubectl get pods --context [CONTEXT]

$ kubectl get pods --context minikube

NAME                      READY   STATUS    RESTARTS   AGE
backend-6c8d9f486-fb47p   1/1     Running   0          7m50s

정상적으로 생성이 완료되었다면 port-forward 명령어를 이용하여 포트 포워딩 후,

GET 요청을 테스트해보면 정상적으로 성공하는 것을 볼 수 있습니다.

# [기본형] kubectl port-forward pod/[POD_NAME] [PORT]:[PORT] --context [CONTEXT]

$ kubectl port-forward pod/backend-6c8d9f486-fb47p 3000:3000 --context minikube
$ curl -XGET 'localhost:3000/get'

{"type":"GET","data":"getResponse"}

쿠버네티스 클러스터에 배포까지 완료되었습니다.

마치며

1, 2편에서 한 실습을 간단하게 정리해보겠습니다.

  1. 패키지 설치
  2. 프로젝트 생성
  3. 기능 구현
  4. 바이너리 생성
  5. 도커 이미지 생성
  6. 도커 이미지 푸시
  7. 도커 이미지 풀
  8. 쿠버네티스 클러스터에서 컨테이너 레지스트리 이미지 가져오기
  9. 쿠버네티스 클러스터에 프로젝트 배포

지금 소개해드린 내용은 무스마 백엔드 개발을 정말 간략하게 볼 수 있는 Getting Start인데요.

이것 외에도

  • GitHub Actions를 이용한 이미지 생성
  • Argo CD를 이용한 디플로이먼트 배포
  • ExternalDNS를 이용한 도메인 자동 생성
  • 등등

소개해드릴 내용이 너~무 많지만 앞으로 차근 차근 포스팅 해보도록 하겠습니다.

감사합니다.

Reference