[Infra] 쿠버네티스 적용기(Feat. Helm)
이번 프로젝트에서 도커로 운영하던 서버를 쿠버네티스로 전환하여 서버를 운영하기로 했다.
현재 프로젝트의 상황은 아래와 같다.
- 메인 EC2
- nginx, springboot, jenkins
- prometheus, promtail, loki, grafana
- kafka
- 서브 EC2(프리 티어)
- Config Server
- Sonarqube
- Rabbitmq
쿠버네티스로 전환하기 위해 사용할 수 있는 스펙을 가진 서버는 메인 EC2 서버 한 대 였기 때문에 단일 노드(마스터 + 워커) 클러스터로 구성하기로 했다.
쿠버네티스는 원래 분산, 복제, 무중단 업데이트를 목표로 설계된 것이기 때문에 단일 노드로는 그 장점을 살릴 수 없었다. 그래도 최대한 단일 노드 클러스터를 극한까지 활용하는 방향으로 구축하려 했다.
대략적인 다이어그램은 아래와 같다.
- Helm Chart를 통한 배포 자동화
- ArgoCD를 통한 GitOps 자동화
- Deployment.yaml에서 RollingUpdate 설정
- Pod Health Check(livenessProbe, readinessProbe)
- PodDisruptionBudget(PDB) 설정
- HPA 설정
- Ingress + SSL
- 메트릭과 로그 수집 및 모니터링
- Alert 시스템
- 자동 백업 스크립트
쿠버네티스 설치
Download Kubernetes
Kubernetes ships binaries for each component as well as a standard set of client applications to bootstrap or interact with a cluster. Components like the API server are capable of running within container images inside of a cluster. Those components are a
kubernetes.io
1. 사전 설정
- 컨테이너 네트워크 간 통신 (Pod <-> Pod)
- 서비스 라우팅 (ClusterIP, NodePort 등)
- iptables 정책을 브릿지 네트워크에도 적용
# 사전 설정 (필수 커널 및 모듈 설정)
# 필요한 커널 모듈 로드
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay # 컨테이너 파일시스템에서 오버레이 파일 시스템을 지원 (Docker/Containered에서 기본)
br_netfilter # 브릿지 네트워크를 통한 패킷도 ptables에서 필터링을 할 수 있게 해줌 (쿠버네티스 Pod 네트워크 필수)
EOF
# 바로 커널에 로드, k8s.conf는 재부팅 후에도 자동 로드되도록 등록
sudo modprobe overlay
sudo modprobe br_netfilter
# 네트워크 관련 sysctl 설정
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1 # 브릿지 네트워크에서 전달되는 패킷도 iptables 규칙을 따르게 함(필수)
net.ipv4.ip_forward = 1 # 위와 같지만 IPv6용
net.bridge.bridge-nf-call-ip6tables = 1 # 리눅스가 라우터처럼 IP 포워딩을 할 수 있게 해줌(Pod <-> 외부 통신 위해 필수)
EOF
sudo sysctl --system
2. 쿠버네티스 설치( kubectl, kubeadm, kubelet)
- kubectl : Kubernetes 클러스터를 관리/제어하는 CLI, 클러스터에 명령어 요청
- kubeadm : Kubernetes 클러스터를 설치/초기화하는 CLI, 마스터 노드 초기화, 워커 노드 조인
- kubelet : 각 노드에서 Pod를 실행하고 관리하는 에이전트, Pod 스케줄링, 실행 상태 체크, 리포트
# 쿠버네티스 설치(kubectl, kubeadm, kubelet)
# 키 등록 및 패키지 소스 추가
sudo curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/kubernetes.gpg
echo "deb [signed-by=/etc/apt/trusted.gpg.d/kubernetes.gpg] https://pkgs.k8s.io/core:/stable:/v1.29/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list
# 설치
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
3. 쿠버네티스 초기화
이 과정에서 출력되는 kubeadm join ... 은 나중에 노드 추가할 때 필요(현재는 싱글 노드라 사용하지 않음)
# 쿠버네티스 초기화
# 마스터 노드 초기화 (flannel 네트워크 범위 사용)
sudo kubeadm init --pod-network-cidr=10.244.0.0/16
4. kubectl 클러스터 연결 설정
# 클러스터 연결 설정
mkdir -p $HOME/.kube
sudo cp /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
5. 단일 노드에서 워커 역할 허용
# 단일 노드에서 워커 역할 허용
kubectl taint nodes --all node-role.kubernetes.io/control-plane-
6. CNI 설치 (Calico)
# Calico 설치
curl https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml -O
kubectl apply -f calico.yaml
7. 설치 확인
# 클러스터 구축 확인
kubectl get nodes
kubectl get pods -A
파드를 조회해보니 위 오류가 계속 발생
calico-kube-controllers와 corens가 pending상태!! -> 스케줄러가 Pod를 배치하지 못하고 있음.
아래 링크를 통해 해결했다!!
https://stackoverflow.com/questions/72844687/invalid-capacity-0-on-image-filesystem-lens-id-kubernetes
invalid capacity 0 on image filesystem, Lens ID Kubernetes
I am creating k8s cluster from digital ocean but every time I am getting same warning after I create cluster and open that cluster in lens ID. Here is the screenshot of warning: I tried every solu...
stackoverflow.com
containerd가 /var/lib/containerd 디스크 상태를 kubelet에 제대로 넘겨주지 못하고 있었는데,
containerd와 kubelet을 둘 다 재시작하니까 정보가 다시 동기화되어 해결함.
kubelet이 디스크 용량을 정확히 인식 못 했던 게 문제였고, 재시작으로 그 캐시가 깨끗하게 초기화되면서 문제가 해결됨!!
쿠버네티스에서의 배포는 원래 YAML 파일이 수십~수백 개씩 생기고 복잡해진다.
이걸 각각 작성하고, 수정하고, 환경별로 나누고 배포하기에는 너무 복잡해지기 떄문에 Helm을 사용하여 관리한다.
Helm은 이런 모든 리소스를 하나의 Chart로 묶어서 관리해주는 도구!!
Helm의 장점
- 배포 자동화 : 하나의 명령어로 여러 리소스를 한 번에 배포
- 패키징 : 앱을 Chart로 만들어 어디서든 재사용 가능
- 환경별 분리 : dev, staging, prod에 맞는 values.yaml 적용 가능
- 업데이트 및 롤백 : 버전별 관리, helm rollback으로 복구 가능
- 템플릿화 : YAML을 템플릿 {{ }}으로 만들어 유연한 배포 가능
Helm Chart 주요 파일
파일명 | 용도 | 상세 기능 |
Chart.yaml | Chart 메타데이터 정의 | Chart 이름, 버전, 설정, 의존성을 설정하는 파일. Helm이 이걸 읽고 Chart를 인식함. |
values.yaml | 기본 설정값 모음 | 템플릿 파일들이 참조할 기본 변수값을 정의. 환경(dev/prod)별로 override 가능. |
deployment.yaml | Kubernetes Deployment 생성 템플릿 | 앱(Pod)을 배포하는 핵심 리소스. replica 수, container image, 롤링 업데이트 전략 등을 정의. |
statefulset.yaml | 상태를 가지는 애플리케이션 배포 리소스 | Pod의 이름, 순서, 볼륨을 고정해서 관리해야 할 때 사용. 데이터가 필요한 앱을 Pod 이름 + 스토리지 고정해서 관리하려고 사용. |
service.yaml | Kubernetes Service 생성 템플릿 | Pod들을 외부에 노출하거나, ClusterIP/LoadBalancer/NLB 형태로 통신 가능하게 해줌. |
ingress.yaml | Kubernetes Ingress 생성 템플릿(선택) | HTTP/HTTPS 요청을 Service로 라우팅. 도메인 기반 접근 설정 가능. |
configmap.yaml | Kubernetes ConfigMMap 생성 템플릿(선택) | 앱 설정 파일, 환경변수처럼 비밀이 아닌 설정 데이터를 저장하는 리소스. |
secret.yaml | Kubernetes Secret 생성 템플릿(선택) | 민감 데이터(비밀번호, 인증키)를 저장하는 리소스. base64 인코딩된 형태로 저장. |
hpa.yaml | Horizontal Pod Autoscaler 생성 템플릿(선택) | CPU 사용량 기반으로 Pod 수를 자도응로 늘리거나 줄이는 리소스. |
serviceaccount.yaml | ServiceAccount 생성 템플릿(선택) | Pod가 사용할 Kubernetes API 접근 권한을 설정. 보안상 필요할 때 생성. |
role/rolebinding.yaml | RBAC 권한 설정 템플릿(선택) | ServiceAccount에 필요한 권한을 부여할 때 사용. Role과 RoleBinding 리소스를 생성. |
pvc.yaml | PersistentVolumeClaim 생성 템플릿(선택) | 앱이 데이터를 저장할 수 있도록 외부 저장소를 요청하는 리소스. EBS, NFS 등에 연결할 때 필요. |
tests/디렉토리 안 파일 | Chart 설치 후 테스트하는 Job 리소스 | Helm의 helm test 명령어로 실행할 수 있는 간단한 테스트 케이스들 작성. |
_helpers.tpl | 템플릿 헬퍼 함수 모음 파일 | 이름 자동 생성(Name Templates), Label 공통화 등 DRY(Don't Repeat Yourself)하게 만드는 도우미 함수 파일. |
해당 프로젝트에서는 Spring Cloud Config를 사용하여 환경변수를 관리하고 있었기 때문에 Helm에서는 Deployment, Service 등만 관리하도록 하면됐다.
Helm 설치
헬름 설치하기
헬름 설치하고 작동하는 방법 배우기.
helm.sh
$ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
$ chmod 700 get_helm.sh
$ ./get_helm.sh
설치 확인
helm version
Spring Cloud Config 설정은 아래와 같이 하면 된다.
Secret 생성
kubectl create secret generic config-server-credentials \
--from-literal=username=config_server_username\
--from-literal=password=config_server_password
values.yaml 일부
# 환경별로 Secret 이름만 바꾸면 되도록 분리
secretName: config-server-credentials
env:
- name: SPRING_PROFILES_ACTIVE
value: dev
- name: SPRING_CONFIG_IMPORT
value: optional:configserver:http://configserver가 올라간 ec2 Public IP:포트번호
- name: CONFIG_USERNAME
valueFrom:
secretKeyRef:
name: config-server-credentials
key: username
- name: CONFIG_PASSWORD
valueFrom:
secretKeyRef:
name: config-server-credentials
key: password
ingress 설정 일부
ingress:
tls:
- host:
- 도메인
secretName: sercret_name
도메인을 SSL/TLS 등록을 해놨다면 Secret 등록 및 ingress 설정에서 위와 같은 설정을 해줘야한다.
TLS Secret 생성
sudo kubectl create secret tls <SECRET_NAME> \
--cert=<CERT_PATH> \
--key=<KEY_PATH> \
-n <namespace>
namespace를 추가할 때마다 위 명령어로 매번 secret을 생성하는 것이 번거롭다고 생각했고, 스크립트를 만들어서 namespace만 추가하면 간단한 명령어로 secret을 생성할 수 있도록 했다.
create-tls.sh
CERT_PATH=<fullchain.pem 디렉토리 경로>
KEY_PATH=<privkey.pem 디렉토리 경로>
SECRET_NAME=<secret_name>
NAMESPACES=("추가할 namespace1" "namespace2" "namespace3" ...)
for ns in "${NAMESPACES[@]}"; do
if ! kubectl get ns "$ns" > /dev/null 2>&1; then
kubectl create namespace "$ns"
fi
kubectl delete secret "$SECRET_NAME" -n "$ns" --ignore-not-found
sudo kubectl create secret tls "$SECRET_NAME" \
--cert="$CERT_PATH" \
--key="$KEY_PATH" \
-n "$ns"
done
또한, 도커 허브에서 이미지를 불러오는데 해당 프로젝트에 대한 도커 허브 레포지토리는 private으로 설정해놨기 때문에 로그인 정보가 필요했다.
GitHub를 통해 Docker Hub로 로그인을 해서 Git 계정에 대한 Access Token이 필요!!
Docker Hub Access
kubectl create secret docker-registry docker-hub \
--docker-username=<GitHub Username> \
--docker-password=<GitHub personal Access Token> \
--docker-email=<GitHub Email> \
-n <Docker Hub에 접근할 namespace>
backend helm-chart/values.yaml
replicaCount: 2
image:
repository: Docker Hub Repository
tag: Image tag
pullPolicy: IfNotPresent
service:
type: ClusterIP/NodePort/...
port: 8080
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
serviceAccount:
create: true
name: ""
ingress:
enabled: true
className: nginx
hosts:
- host: 도메인
paths:
- path:
pathType: Prefix/ImplementationSpecific/...
tls:
- host:
- 도메인
secretName: <secret_name>
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 5
targetCPUUtilizationPercentage: 80
imagePullSecrets:
- name: <Docker Hub 접송 관련해 생성한 ecrect_name>
containerPort: 8080
# 환경별로 Secret 이름만 바꾸면 되도록 분리
secretName: config-server-credentials
env:
- name: SPRING_PROFILES_ACTIVE
value: <Profile_name>
- name: SPRING_CONFIG_IMPORT
value: optional:configserver:http://configserver가 올라간 ec2 Public IP:포트번호
- name: CONFIG_USERNAME
valueFrom:
secretKeyRef:
name: config-server-credentials
key: username
- name: CONFIG_PASSWORD
valueFrom:
secretKeyRef:
name: config-server-credentials
key: password
나머지 YAML파일 설정은 배포 환경마다 다르기 때문에 스킵하도록 하겠음!!
파일 설정후 아래 명령어로 렌더링 잘 되는지 확인 및 배포!
# 렌더링 확은
helm template <namespace> <helm chart가 위치한 프로젝트 루트 디렉토리>
# 배포
helm upgrade --install <namespace> <helm chart가 위치한 프로젝트 루트 디렉토리>
참고:
https://etloveguitar.tistory.com/141
https://dev-scratch.tistory.com/177