험난한 Kubernetes 전환기 (3) - K8s로 서비스 이사하기
지난 몇 개월간 Azure Kubernetes Service와 Amazon EKS 환경을 차례로 구축하며 Kubernetes 전환의 기반을 마련하고, 일부 서비스를 실제로 전환하기도 했습니다.
최근에 본격적으로 기존 서비스들을 차례차례 Kubernetes 환경으로 이전했는데, 그 과정에서 고민한 관리 방식과 과정을 정리해 보았습니다.
Repository 구조 설계
Kubernetes 환경으로 전환하면서 가장 많이 했던 고민은 배포 관련 코드를 어떻게 관리할 것인가였습니다. 이전 단계에서 환경을 구성하고 일부 서비스를 이전하면서도 계속 고민했던 주제였습니다.
기존에는 각 서비스 Repository에 배포 관련 파일들이 분산되어 있었는데, 서비스 수가 많지 않았기 때문에 큰 문제는 없었습니다. 하지만 앞으로 서비스는 계속 늘어날 거고, 현재 사용하는 Helm Chart 등의 형태도 모두 제각각이라 일관성이 없었습니다.
이러한 상태를 유지하면 팀 내에서 유지보수가 힘들어지고, 특정 서비스의 배포 설정을 변경하기 위해 매번 다른 곳을 찾아가야 하는 불편함이 있었습니다.
또한, 다른 팀에서 배포 관련 Commit이 개발 관련 Commit과 섞이기를 원하지도 않는다는 의견도 있었습니다.
이를 포함한 여러 고민 끝에 분리되고 중앙화된 Helm Chart + Argo CD 설정 Repository를 구성하기로 결정했습니다. 처음부터 가능하면 견고한 구조를 잡고 싶었고, 고민 끝에 다음과 같은 형태로 설계했습니다.
saas-helm-charts/
├── .github/workflows/
│ └── update-tag.yaml
├── argocd/
│ ├── applications/
│ │ ├── myorg-single-service.yaml
│ │ └── oss-mlflow.yaml
│ └── applicationsets/
│ ├── myorg-multi-service.yaml
│ └── oss-prometheus.yaml
├── charts/
│ ├── myorg-single-service/
│ ├── myorg-multi-service/
│ │ ├── charts/
│ │ ├── templates/
│ │ ├── values/
│ │ │ ├── prod-us.yaml
│ │ │ ├── prod-ja.yaml
│ │ │ ├── staging.yaml
│ │ │ ├── nginx.yaml
│ │ │ └── azure-application-gateway.yaml
│ │ └── Chart.yaml
│ └── oss-prometheus/
│ ├── README.md
│ └── values/
│ ├── prod-us.yaml
│ ├── prod-ja.yaml
│ └── ...
└── scripts/
├── update-tag.sh
└── ...
우선 argocd
폴더를 두고 Application과 ApplicationSet을 관리하도록 했습니다.
이 부분까지 별도의 Repository로 분리하는 것도 고민했지만, 규모가 커졌을 때 고려해도 된다고 생각했습니다.
이렇게 하여 최초 2개의 폴더를 등록하면, 그 뒤로는 알아서 Git과 동기화되어 배포가 이루어지게 됩니다.
Helm chart 폴더의 경우, 회사 자체 서비스는 회사 이름을 Prefix로 붙이고, 오픈소스의 경우 oss-
Prefix를 붙였습니다.
이렇게 하여 폴더 구조가 과도하게 깊어지는 것을 방지하고, 회사 자체 서비스와 오픈소스 서비스를 명확하게 구분할 수 있도록 했습니다.
오픈소스 Helm chart의 경우 커뮤니티에서 직접 관리하도록 하고, 필요한 values 파일과 불가피하게 추가할 만한 리소스만 관리하도록 했습니다. 대신 README를 두어 어떤 소스 코드를 사용했는지, 어떤 설정을 했는지 등을 파악할 수 있도록 정리해 두었습니다.
Helm Chart 표준화
기존에 임시로 사용하던 Helm chart들은 관리가 잘 되지 않고 있었고, 각 서비스마다 서로 다른 포맷을 사용하고 있었습니다.
그리고 그 포맷도 당시 제작자가 임의로 정의한 부분이 많아 이 값이 어디에 영향을 미치는지는 템플릿을 뜯어보지 않으면 알 수 없었습니다.
이를 해결하기 위해 helm create
명령어로 생성되는 기본 템플릿을 기반으로 표준화 작업을 진행했습니다.
helm create myorg-service-template
이렇게 만들어진 기본 템플릿을 바탕으로 최소한의 수정만 가하여 유지보수성을 확보했습니다.
주로 추가한 요소는 deployment
블록과 Azure Application Gateway 연동을 위한 AzureApplicationGatewayRewrite
리소스 관련 설정 정도였고,
그 외에는 추가적인 템플릿 파일이 필요하면 그에 대한 블록을 정의하는 식으로 확장해 팀 내에서 무리 없이 이용할 수 있도록 했습니다.
이 외의 설정, 특히 설명 없이는 알아볼 수 없는 설정을 최대한 피하려 노력했고, 당장 사용하지 않는 템플릿도 추후 사용 가능성이 있다면 삭제를 지양하였습니다.
이렇게 템플릿을 만든 뒤에는 새로운 서비스를 추가할 때에도 기존 템플릿을 복사하고 약간 수정하는 정도로 충분했습니다. 또한, SaaS 환경이 아닌 곳에서 Helm chart가 필요한 경우에도 참고할 수 있는 자료가 되었습니다.
ApplicationSet 활용하기
여러 환경의 배포, 또는 여러 클러스터에 배포하는 경우 Argo CD의 ApplicationSet을 사용하였습니다.
특히 필요한 경우 2개 이상의 values 파일을 조합하고, Multi-source 방식을 적극 활용하여 유연한 배포가 가능하도록 설계했습니다.
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: myorg-service-applicationset
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
# ...
generators:
- list:
elements:
- name: prod-us
project: prod-us
cluster: myorg-prd-eks
namespace: myorg-prod-us
valuesFile: prod-us.yaml
ingressClass: nginx
branch: main
envFile: path/to/env/file
template:
metadata:
name: "myorg-{{ .name }}"
spec:
project: "{{ .project }}"
destination:
name: "{{ .cluster }}"
namespace: "{{ .namespace }}"
sources:
- repoURL: https://github.com/myorg/helm-charts
targetRevision: "{{ .branch }}"
path: charts/myorg-service
helm:
releaseName: "{{ .name }}"
valueFiles:
- "values/{{ .valuesFile }}"
- "values/{{ .ingressClass }}.yaml"
fileParameters:
- name: envFile
path: "$env/{{ .envFile }}"
- repoURL: https://github.com/myorg/other-repo
targetRevision: master
ref: env
syncPolicy:
# ...
위는 예시 ApplicationSet 설정입니다.
valueFiles
의 경우 서비스 배 포 형태를 생각했을 때 다음 2가지 요소가 있다고 생각했습니다.
- 환경 특화 설정 (
prod-us.yaml
,prod-ja.yaml
,staging.yaml
등) - 네트워크 특화 설정 (
nginx.yaml
,azure-application-gateway.yaml
등)
그렇다면 이 각각의 설정을 따로 정의하고, 조합하여 배포하는 것이 가장 좋을 것 같다고 생각했습니다.
fileParameters
의 경우1, 외부 Repository의 파일을 참조하여 ConfigMap이나 다른 리소스를 생성해야 하는 경우에 사용했습니다.
서비스 전환하기
모든 서비스를 한 번에 전환하는 것은 위험성이 높기 때문에, 점진적 전환을 통해 트래픽을 전환했습니다.
이 과정에서 Route 53의 가중치 기반 라우팅2을 통해 트래픽을 분산하는 방식을 채택하거나, nginx.conf
파일 등 네트워크 설정이 따로 있다면 해당 파일에서 직접 비율을 조절하였습니다.
네트워크와 관련이 없는 Worker 종류의 서비스는 K8s 환경에 배포한 뒤, 정상 동작이 확인되면 Docker Compose 자원을 삭제하는 방식으로 전환했습니다.
마치며
이러한 과정을 거쳐, 이번 달 주요 서비스의 트래픽이 Kubernetes 환경으로 전환되었습니다 🎉🎉🎉
올해 팀 내 큰 목표이기도 했고, 특히 기존 Docker Compose 환경이 많이 노후화되어 문제를 일으키는 경우도 많았는데 이제 어느 정도 유연성과 안정성이 생기지 않을까 싶습니다.
1차 목표는 달성했지만, 아직 해야 할 일들이 많이 남아 있습니다.
- 모든 리소스가 전환된 것이 아니기 때문에, 남은 것들을 정리해 나가야 합니다.
- Blue/Green 배포, 이벤트 기반 Auto-scaling 등 더 나은 배포 방식을 적용해 나가야 합니다.
- 모니터링 시스템도 구축해 나가려고 합니다. 현재는 Datadog를 사용하고 있는데, 회사 차원에서 이를 Grafana와 같은 다른 모니터링 도구로 대체해 나가려고 합니다.
그래도 제가 처음 들어왔을 때 목표로 했던 일의 한 단락이 마무리된 것 같아 뿌듯하네요.
이제 다음 단계를 위해 또 열심히 해 나가려고 합니다.