Kubernetes로 배포하기
Kubernetes는 컨테이너화된 애플리케이션을 배포, 확장, 관리하는 시스템이에요. Backstage는 이 모델에 맞게 설계되었으며, 외부 PostgreSQL 데이터베이스와 함께 무상태(stateless) 애플리케이션으로 실행되도록 만들어져 있어요.
출처: 문서
본문
Kubernetes는 컨테이너화된 애플리케이션을 배포, 확장, 관리하는 시스템이에요. Backstage는 이 모델에 맞게 설계되었으며, 외부 PostgreSQL 데이터베이스와 함께 무상태(stateless) 애플리케이션으로 실행되도록 만들어져 있어요.
Kubernetes 클러스터에는 많은 다른 도구와 패턴이 있어서, 기존 Kubernetes 설정에 배포하는 가장 좋은 방법은 다른 모든 것을 배포하는 것과 같은 방식이에요.
이 가이드는 일반적인 클러스터에서 Backstage를 가동하는 데 필요한 기본 Kubernetes 정의를 다뤄요. Backstage 소프트웨어 카탈로그도 엔티티 정의 파일에 Kubernetes 객체 형식을 사용하기 때문에, 객체 정의가 익숙해 보일 거예요!
로컬에서 테스트하기
프로덕션 Kubernetes 클러스터에 배포하기 전에 로컬에서 이 개념들을 테스트하려면, 먼저 Kubernetes 커맨드라인 도구인 kubectl을 설치하세요.
다음으로 minikube를 설치하세요. 이것은 로컬 머신에 단일 노드 Kubernetes 클러스터를 만들어요.
# Assumes Mac + Homebrew; see the minikube site for other installations
$ brew install minikube
$ minikube start
...Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default.
이제 kubectl 명령을 실행할 수 있고 변경 사항이 minikube 클러스터에 적용돼요. 실행 중인 kube-system Kubernetes 파드를 볼 수 있어야 해요.
$ kubectl get pods -A
튜토리얼이 끝나면 minikube stop을 사용해 클러스터를 멈추고 리소스를 확보하세요.
네임스페이스 만들기
Kubernetes의 배포는 멀티 테넌트 환경에서 서비스를 격리하기 위해 흔히 자체 네임스페이스에 할당돼요.
이것은 kubectl로 직접 할 수 있어요.
$ kubectl create namespace backstage
namespace/backstage created
대안으로 Namespace 정의를 만들어 적용할 수도 있어요.
# kubernetes/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: backstage
$ kubectl apply -f kubernetes/namespace.yaml
namespace/backstage created
PostgreSQL 데이터베이스 만들기
프로덕션의 Backstage는 데이터베이스로 PostgreSQL을 사용해요. 데이터베이스를 Backstage 앱 배포와 격리하기 위해 PostgreSQL용 별도 Kubernetes 배포를 만들 수 있어요.
PostgreSQL 시크릿 만들기
먼저 PostgreSQL 사용자 이름과 비밀번호를 위한 Kubernetes Secret을 만드세요. 이는 PostgreSQL 데이터베이스와 Backstage 배포 모두에서 사용될 거예요.
# kubernetes/postgres-secrets.yaml
apiVersion: v1
kind: Secret
metadata:
name: postgres-secrets
namespace: backstage
type: Opaque
data:
POSTGRES_USER: YmFja3N0YWdl
POSTGRES_PASSWORD: aHVudGVyMg==
Kubernetes 시크릿의 데이터는 base64로 인코딩돼요. 그 값들은 커맨드라인에서 생성할 수 있어요.
$ echo -n "backstage" | base64
YmFja3N0YWdl
참고
시크릿은 base64로 인코딩되지만 암호화되지는 않아요. 클러스터에 대해 저장 시 암호화(Encryption at Rest)를 반드시 활성화하세요. Git에 시크릿을 저장하려면 SealedSecrets나 다른 솔루션을 고려하세요.
이제 시크릿을 Kubernetes 클러스터에 적용할 수 있어요.
$ kubectl apply -f kubernetes/postgres-secrets.yaml
secret/postgres-secrets created
PostgreSQL 영구 볼륨 만들기
PostgreSQL은 데이터를 저장할 영구 볼륨이 필요해요. PersistentVolumeClaim과 함께 하나를 만들 거예요. 이 경우 전체 볼륨을 청구하지만, 청구는 볼륨의 일부만 요청할 수도 있어요.
# kubernetes/postgres-storage.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: postgres-storage
namespace: backstage
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 2G
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
hostPath:
path: '/mnt/data'
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-storage-claim
namespace: backstage
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2G
이 파일은 삼중 대시 줄로 구분된 두 가지 종류의 정의를 포함해요. 이 구문은 관련 Kubernetes 정의를 단일 파일로 통합해 동시에 적용하고 싶을 때 유용해요.
type: local 볼륨을 주목하세요. 이는 Kubernetes 노드의 로컬 디스크를 사용해 볼륨을 만들어요. 프로덕션 시나리오에서는 더 높은 가용성을 가진 유형의 PersistentVolume을 사용하고 싶을 가능성이 더 높아요.
저장소 볼륨과 청구를 Kubernetes 클러스터에 적용하세요.
$ kubectl apply -f kubernetes/postgres-storage.yaml
persistentvolume/postgres-storage created
persistentvolumeclaim/postgres-storage-claim created
PostgreSQL 배포 만들기
이제 PostgreSQL 데이터베이스 배포 자체에 대한 Kubernetes Deployment 기술자를 만들 수 있어요.
# kubernetes/postgres.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: backstage
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:13.2-alpine
imagePullPolicy: 'IfNotPresent'
ports:
- containerPort: 5432
envFrom:
- secretRef:
name: postgres-secrets
env:
- name: POSTGRES_HOST
value: postgres.backstage
- name: POSTGRES_PORT
value: '5432'
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: postgresdb
subPath: data
volumes:
- name: postgresdb
persistentVolumeClaim:
claimName: postgres-storage-claim
Kubernetes에 익숙하지 않다면 이것은 받아들이기 어려운 양이에요. 우리는 metadata 블록에서 Kubernetes가 알아야 할 Deployment(애플리케이션의 인스턴스 하나 이상)를 설명하고 있어요.
spec 블록은 원하는 상태(desired state)를 설명해요. 여기서 Kubernetes에 복제본 1개(PostgreSQL의 실행 인스턴스)를 만들도록 요청하고, 주어진 파드 template으로 복제본을 만들도록 요청했어요. 템플릿은 다시 Kubernetes 메타데이터와 원하는 상태를 담고 있어요. 템플릿 spec은 게시된 postgres:13.2-alpine Docker 이미지에서 만든 컨테이너 하나를 보여줘요.
envFrom과 secretRef를 주목하세요. 이것은 Kubernetes에게 우리가 만든 Secret의 값으로 컨테이너의 환경 변수를 채우라고 말해요. 배포를 위해 만든 볼륨도 참조하고, PostgreSQL이 기대하는 마운트 경로를 주었어요.
PostgreSQL 배포를 Kubernetes 클러스터에 적용하세요.
$ kubectl apply -f kubernetes/postgres.yaml
deployment.apps/postgres created
$ kubectl get pods --namespace=backstage
NAME READY STATUS RESTARTS AGE
postgres-56c86b8bbc-66pt2 1/1 Running 0 21s
파드에 연결해 배포를 확인하세요.
$ kubectl exec -it --namespace=backstage postgres-56c86b8bbc-66pt2 -- /bin/bash
bash-5.1# psql -U $POSTGRES_USER
psql (13.2)
backstage=# \q
bash-5.1# exit
PostgreSQL 서비스 만들기
데이터베이스 파드는 실행 중이지만, 다른 파드는 어떻게 연결할까요?
Kubernetes 파드는 일시적이에요. 중지, 재시작, 동적으로 생성될 수 있어요. 따라서 직접 파드에 연결하려 하지 말고 Kubernetes Service를 만들어야 해요. Service는 파드를 추적하고 트래픽을 올바른 곳으로 보내요.
데이터베이스의 마지막 단계는 서비스 기술자를 만드는 것이에요.
# kubernetes/postgres-service.yaml
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: backstage
spec:
selector:
app: postgres
ports:
- port: 5432
서비스를 Kubernetes 클러스터에 적용하세요.
$ kubectl apply -f kubernetes/postgres-service.yaml
service/postgres created
$ kubectl get services --namespace=backstage
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
postgres ClusterIP 10.96.5.103 <none> 5432/TCP 29s
Backstage 인스턴스 만들기
이제 PostgreSQL이 준비되어 데이터를 저장할 수 있으니 Backstage 인스턴스를 만들 수 있어요. 이것은 PostgreSQL 배포와 비슷한 단계를 따릅니다.
Backstage 시크릿 만들기
인증 토큰 같은 Backstage 구성 시크릿의 경우, PostgreSQL에서 한 것처럼 비슷한 Kubernetes Secret을 만들 수 있어요. 값을 base64 인코딩하는 것을 기억하세요.
# kubernetes/backstage-secrets.yaml
apiVersion: v1
kind: Secret
metadata:
name: backstage-secrets
namespace: backstage
type: Opaque
data:
GITHUB_TOKEN: VG9rZW5Ub2tlblRva2VuVG9rZW5NYWxrb3ZpY2hUb2tlbg==
시크릿을 Kubernetes 클러스터에 적용하세요.
$ kubectl apply -f kubernetes/backstage-secrets.yaml
secret/backstage-secrets created
Backstage 배포 만들기
Backstage 배포를 만들려면 먼저 Docker 이미지를 만드세요. 이 이미지를 사용해 Kubernetes 배포를 만들 거예요. 이 예시에서는 프론트엔드가 번들되어 백엔드에서 서빙되는 표준 호스트 빌드를 사용할 거예요.
먼저 Kubernetes Deployment 기술자를 만드세요.
# kubernetes/backstage.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: backstage
namespace: backstage
spec:
replicas: 1
selector:
matchLabels:
app: backstage
template:
metadata:
labels:
app: backstage
spec:
containers:
- name: backstage
image: backstage:1.0.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 7007
envFrom:
- secretRef:
name: postgres-secrets
- secretRef:
name: backstage-secrets
# Uncomment if health checks are enabled in your app:
# https://backstage.io/docs/plugins/observability#health-checks
# readinessProbe:
# httpGet:
# port: 7007
# path: /healthcheck
# livenessProbe:
# httpGet:
# port: 7007
# path: /healthcheck
프로덕션 배포에서 image 참조는 보통 컨테이너 레지스트리(AWS의 ECR 같은)에 있는 저장소의 전체 URL이에요.
minikube로 로컬에서 테스트하려면 로컬 Docker 데몬을 minikube 내부 Docker 레지스트리로 가리키고 이미지를 다시 빌드해 설치할 수 있어요.
$ eval $(minikube docker-env)
$ yarn build-image --tag backstage:1.0.0
PostgreSQL 서비스에 접근하는 데 특별한 배선이 필요 없어요. 같은 클러스터에서 실행되므로 Kubernetes가 POSTGRES_HOST와 POSTGRES_PORT 환경 변수를 우리 Backstage 컨테이너에 주입할 거예요. 이것들은 Backstage app-config.yaml에서 시크릿과 함께 사용할 수 있어요. app-config.production.yaml도 있다면 그곳에도 적용하세요.
backend:
database:
client: pg
connection:
host: ${POSTGRES_HOST}
port: ${POSTGRES_PORT}
user: ${POSTGRES_USER}
password: ${POSTGRES_PASSWORD}
app-config.yaml 변경을 적용한 후 Docker 이미지를 다시 빌드해야 해요.
이 Deployment를 Kubernetes 클러스터에 적용하세요.
$ kubectl apply -f kubernetes/backstage.yaml
deployment.apps/backstage created
$ kubectl get deployments --namespace=backstage
NAME READY UP-TO-DATE AVAILABLE AGE
backstage 1/1 1 1 1m
postgres 1/1 1 1 10m
$ kubectl get pods --namespace=backstage
NAME READY STATUS RESTARTS AGE
backstage-54bfcd6476-n2jkm 1/1 Running 0 58s
postgres-56c86b8bbc-66pt2 1/1 Running 0 9m
아름다워요! 🎉 배포와 파드가 클러스터에서 실행 중이에요. 문제가 생기면 파드의 컨테이너 로그를 확인하세요.
# -f to tail, <pod> -c <container>
$ kubectl logs --namespace=backstage -f backstage-54bfcd6476-n2jkm -c backstage
Backstage 서비스 만들기
위의 PostgreSQL 서비스와 마찬가지로, Backstage가 요청을 올바른 파드로 연결하기 위해 Kubernetes Service를 만들어야 해요.
Kubernetes Service 기술자를 만드세요.
# kubernetes/backstage-service.yaml
apiVersion: v1
kind: Service
metadata:
name: backstage
namespace: backstage
spec:
selector:
app: backstage
ports:
- name: http
port: 80
targetPort: http
여기서 selector는 Service가 어떤 파드를 대상으로 할지 알려주고, 포트 매핑은 일반 HTTP 포트 80을 파드의 백엔드 http 포트(7007)로 변환해요.
이 Service를 Kubernetes 클러스터에 적용하세요.
$ kubectl apply -f kubernetes/backstage-service.yaml
service/backstage created
이제 완전히 작동하는 Backstage 배포가 준비됐어요! 🎉 멋진 공개를 위해 로컬 포트를 서비스로 전달할 수 있어요.
$ sudo kubectl port-forward --namespace=backstage svc/backstage 80:80
Forwarding from 127.0.0.1:80 -> 7007
port-forward는 서비스를 실제로 지원하지 않아서 서비스의 첫 번째 파드를 찾아 매핑된 파드 포트에 연결하는 방식으로 우회하기 때문에 포트 7007이 표시돼요.
app-config.yaml의 app.baseUrl과 backend.baseUrl은 우리가 여기서 전달하는 것과 일치해야 해요(이 예시에서는 기본 HTTP 포트 80을 사용하므로 포트는 생략했어요).
# app-config.yaml
app:
baseUrl: http://localhost
organization:
name: Spotify
backend:
baseUrl: http://localhost
listen:
port: 7007
cors:
origin: http://localhost
인증 프로바이더를 사용한다면, 인증 팝업이 제대로 작동하려면 이 주소도 구성되어 있어야 해요.
이제 머신에서 브라우저를 열어 localhost로 이동하고 Kubernetes에 배포된 Backstage 인스턴스를 탐색할 수 있어요. 🚢🚢🚢
추가 단계
이것은 Kubernetes에서 Backstage의 완전한 프로덕션 배포까지 대부분의 길을 오는 거예요. 이 문서의 범위를 벗어날 가능성이 높은 몇 가지 추가 단계가 있어요.
더 안정적인 볼륨 설정
위에서 구성한 PersistentVolume은 local Kubernetes 노드 저장소를 사용해요. 이것은 클라우드 볼륨, 네트워크 연결 저장소, 또는 Kubernetes 노드를 넘어 더 영구적인 것으로 교체해야 해요.
Backstage 서비스 노출
Kubernetes Service는 클러스터 밖에서의 외부 연결을 위해 노출되지 않아요. 이것은 일반적으로 Kubernetes 인그레스(ingress)나 외부 로드 밸런서로 처리돼요.
Deployment 이미지 업데이트
Kubernetes 배포를 새로 게시된 Backstage Docker 이미지 버전으로 업데이트하려면 backstage.yaml의 이미지 태그 참조를 업데이트한 다음 kubectl apply -f kubernetes/backstage.yaml로 변경 사항을 적용하세요.
프로덕션 목적의 경우 이 이미지 태그는 일반적으로 빌드된 Docker 이미지가 호스팅되는 컨테이너 레지스트리를 가리키는 완전한 URL이에요. 이것은 인프라 내부에 호스팅하거나 클라우드 프로바이더가 제공하는 관리형 레지스트리를 사용할 수 있어요.