다른 사람들이 물어봤을때 바로 대답이 나오지 못하는 내 자신을 보고 머릿속에 정리가 되어있지 않다고 판단하여 쿠버네티스에서 자주 사용되는 주요 오브젝트들을 간단히 정리해보려고 한다.
1. Pod
(Container Group) 쿠버네티스에서 가장 작은 배포 단위로, 하나 이상의 컨테이너(+사이드카 패턴)을 포함할 수 있다. 보통 하나의 파드를 하나의 컨테이너가 이룰 수 있지만 예를 들어, 애플리케이션을 실행하는 메인 컨테이너와 로그를 수집하는 사이드카 컨테이너가 함께 포함될 수 있다.
(Network Namespace & Ports) 하나의 파드에서 컨테이너들은 동일한 네트워크 공간을 공유하고 하나의 IP 주소를 사용한다. 파드의 IP 주소는 파드가 생성될 때 할당되고 파드가 삭제되면 해제된다.
파드 내의 각 컨테이너는 포트를 통해 외부와의 네트워크 통신을 수행할 수 있으며 이를 위해 주로 쿠버네티스 서비스가 필요하다.
(ConfigMap & Secret) 파드는 환경 변수를 통해 외부에서 설정 값을 받을 수 있으며, ConfigMap과 Secret을 이용해 환경 설정과 민감한 정보를 주입할 수 있다. ConfigMap은 일반 설정 정보, Secret은 비밀번호와 같은 민감한 정보를 암호화해서 전달할 수 있다.
(Pod Template) 파드 템플릿은 파드의 정보를 가지고 있고 주로 컨트롤러 오브젝트(Deployment, StatefulSet)에 포함되어 파드를 자동으로 생성하는데 사용된다.
(Labels and Seletors) 파드는 라벨을 통해 같은 애플리케이션 또는 환경에 속한 파드들을 그룹화 할 수 있다.
(Resource Request, Limit) 파드는 실행에 필요한 CPU와 메모리를 요청, 제한할 수 있다.
(Shared Storage Volume) 파드에 연결된 볼륨은 파드 내 모든 컨테이너가 공유가 가능하다.
위 Pod의 특징을 바탕으로 ReplicaSet, Deployment, StatefulSet 등 사용되오니 이러한 기본적인 특징을 잘 활용하여 앞으로 나올 에제 코드들에 참고하도록 하자.
예시)
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod-with-logger
labels:
app: nginx
spec:
containers:
- name: nginx-container
image: nginx:1.21
ports:
- containerPort: 80
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
- name: log-collector
image: busybox
command: ["sh", "-c", "tail -f /var/log/nginx/access.log"]
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
volumes:
- name: shared-logs
emptyDir: {}
두 컨테이너를 정의하고 shared-logs라는 볼륨을 /var/log/nginx 경로에 마운트하여 같은 로그 파일을 접근하도록 설정하였다. emptyDir 볼륨을 사용하여 파드가 삭제될 때까지 유지되는 임시 저장소를 만들어 활용한 예제이다.
볼륨을 공유하는 등의 형식에서 docker-compose를 활용했을때와 비슷한 느낌을 받을 수 있었다. 이제 이 Pod Spec을 활용해서 Deployment, StatefulSet, Rollout 등을 구현할 수 있다.
2. ReplicaSet
위 Pod에 대해 이해했다면 ReplicaSet은 Pod의 복제본을 관리한다고 생각하면 될 것 같다. 보통 실무에서는 Deployment 같은 객체를 통해 ReplicaSet을 생성하고 관리하지만 ReplicaSet의 개념이 정의된 파드의 특정 수만큼 유지되도록 보장하는 것이기에 알고 넘어가면 좋을 것 같다.
Label Selector를 통해 관리할 파드를 선택하며 replicas를 지정하여 원하는 복제본 수를 지정할 수 있다. 이 수만큼 파드가 유지되도록 보장한다. template을 통해 파드의 컨테이너 이미지, 환경 변수, 볼륨 등을 지정하여 하나의 템플릿 처럼 활용할 수 있다.
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: nginx-replicaset
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.21
ports:
- containerPort: 80
3. Deployment
위에서 Pod와 ReplicaSet을 확인했다면 Deployment는 쉽게 다가올 것이다. ReplicaSet을 관리하며 추가적으로 배포, 업데이트, 스케줄링 등의 추가적인 기능을 지원하는 상위 수준의 리소스 오브젝트이다.
사용자가 직접 ReplicaSet을 관리할 필요 없이 배포, 롤링 업데이트, 롤백 등을 처리할 수 있게 해준다.
ReplicaSet을 관리하는 컨트롤러라고 바라봐도 될 것 같다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.21
ports:
- containerPort: 80
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 새 파드를 최대 1개까지 먼저 생성
maxUnavailable: 1 # 업데이트 중 최대 1개 파드만 사용할 수 없게 허용
예제를 보면 배포 전략(strategy) 부분이 추가되었다. 롤링 업데이트 전략을 명시적으로 설정하였는데 배포할 경우 파드를 얼마나 점진적으로 업데이트 할 것인지 설정하였다.
- maxSurge는 롤링 업데이트를 수행할 때 새로운 파드를 얼마나 빨리 생성할지 결정한다. maxSurge: 1로 설정하면, 기존 파드가 종료되기 전에 새로운 파드가 하나 먼저 생성되어. 이를 통해 장애를 최소화할 수 있습니다.
- maxUnavailable는 롤링 업데이트 중 파드가 얼마나 "사용할 수 없게" 될 수 있는지 결정한다. maxUnavailable: 1로 설정하면, 업데이트 중에 한 파드는 잠시 동안 사용할 수 없게 된다.
4. StatefulSet
StatefulSet은 Deployment와 비슷하게 생겼지만 다른 목적을 가지고 있다. 주로 상태를 유지하는 애플리케이션(Stateful application)에 사용된다. 반대로 Deployment는 상태가 없는 애플리케이션(Stateless)를 배포한다고 보면 된다. 보통 Stateless하게 설계해야 수평적 확장에 유리하고 특히 쿠버네티스 환경에서는 파드가 계속 배포되고 스케일링 되는 환경에서 Stateless 한 것이 더 효율적으로 관리될 수 있다고 생각하지만 Stateful 하게 설계해야하는 경우를 피해갈 수 없는 상황이 있다. Helm Charts를 이용하여 ElasticSearch를 설치하였을때 이를 느낄 수 있었는데 샤딩과 레플리카를 분산하여 저장하고 Elastic Cluster 구성에서 배포 순서가 필요했기에 StatefulSet이 필요했다. 이 부분은 따로 더 자세하게 살펴보도록 하고 StatefulSet은 꼭 알아둬야 할 오브젝트 중 하나라고 생각이 들었다.
StatefulSet이 필요한 이유는 애플리케이션이 상태를 가지는 경우이다. 예를 들어 데이터베이스, 메시징 큐, 분산 시스템 등이 대표적인 예다. 각 파드가 고유한 네트워크 ID와 안정적인 스토리지, 그리고 상태를 유지할 필요가 있다.
StatefulSet은 각 파드에 고유한 네트워크 이름을 부여한다. 예를 들어 nginx-0, nginx -1, nginx -2와 같은 방식으로 식별 가능한 상태를 말한다.
위와 같이 deployment로 배포하였을 경우는 다음과 같은 이름으로 배포될 때마다 바뀌어 식별이 불가능하도록 배포되었을 것이다.
StatefuleSet은 PersistentVolume(PV)과 결합하여 각 파드마다 고유한 PersistentVolumeClaim(PVC)를 사용하도록 설정할 수 있다. 따라서 파드가 재시작되거나 삭제되더라도 기존 데이터를 유지하며 네트워크 ID와 스토리지가 보장된 상태로 안정성을 유지하며 재시작할 수 있다.
또한 파드를 순차적으로 생성하거나 종료하는 것도 가능하다. 기존 파드가 준비된 후에 생성되어야 하거나 삭제된 후에 삭제되어야 하는 경우 중요하게 작용할 수 있다.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-statefulset
spec:
serviceName: "nginx"
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: nginx-data
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: nginx-data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
실행 결과를 보면 다음과 같다.
위 그림과 같이 StatefulSet의 경우 순차적으로 배포되며 고유한 ID를 갖는 것을 볼 수 있다.
3개의 replicas를 지정하였기에 각 파드는 고유한 이름(nginx-0, nginx-1, nginx-2)을 가지고 있으며, 동일한 서비스를 제공하지만 각자 고유한 저장소를 갖는다.
StatefulSet은 고유한 DNS 이름을 부여하지만 이를 통해 파드들을 연결하려면 Headless Service를 정의해야 한다.
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
clusterIP: None # Headless Service
selector:
app: nginx
ports:
- port: 80
targetPort: 80
clusterIP를 None으로 정의하면 Headless Service를 생성할 수 있다. Headless Service는 클러스터 내부에서 각 파드의 IP를 직접 접근할 수 있도록 한다.
예를 들어 nginx-statefulset-0의 경우 nginx-statefulset-0.nginx.default.svc.cluster.local 처럼 호출 할 수 있다.
5. DaemonSet
DaemonSet은 쿠버네티스에서 각 노드(워커노드)에 하나씩 파드를 배포한다. 이를 통해 모든 노드에 파드가 단 하나만 배포되도록 보장할 수 있다. 주로 노드의 상태를 모니터링하거나 노드마다 필요한 특정 서비스를 배포할 때 사용한다.
예를 들자면 AWS EC2에 SSM manager로 접속하는 경우를 예시로 들어보자.
각각의 워커노드 ssm-installer가 설치되어 있어야한다. 노드가 스케일아웃되어 새로 생성되어도 필요하다. 이럴때 Daemonset을 활용하면 동적으로 새로운 노드에 파드를 배포하고 노드가 삭제되면 파드도 자동으로 삭제된다.
또 다른 예시는 로깅 아키텍쳐를 구축할 때 쿠버네티스는 모든 파드의 stdout 로그를 노드마다 동일한 경로에 쌓게 되는데 해당 경로에 읽는 로그 파일을 읽는 용도로 fluent-bit 같은 컨테이너를 DaemonSet으로 띄우곤 한다.
위와 마찬가지로 PodTemplate을 활용하고 nodeSelector 등의 선택적 노드 배포도 가능하다.
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nginx
namespace: default
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
spec부터는 위에서 계속 확인했으니 익숙하게 느껴진다.
해당 DamonSet을 생성해서 확인해보자.
위 DaemonSet을 배포하여 확인해보니 각 노드마다 단 하나씩 배포된 것을 확인할 수 있다.
(nginx를 DaemonSet으로 활용한건 단순 예제입니다. 참고 부탁드립니다.)
6. Job & CronJob
Job과 CronJob은 비교적 단순하게 설명된다. Job은 지정된 작업을 한 번만 실행하는 일회성 작업이며 CronJob은 정기적인 작업을 실행할 때 사용되는 오브젝트이다.
Job
apiVersion: batch/v1
kind: Job
metadata:
name: example-job
spec:
completions: 1 # Job의 성공적인 종료를 위한 완료 횟수
parallelism: 1 # 동시에 실행될 파드 수
template:
spec:
containers:
- name: busybox
image: busybox
command: ["sh", "-c", "echo 'Hello Kubernetes' && sleep 30"]
restartPolicy: Never
이 예제에서는 busybox 컨테이너를 사용해 "Hello Kubernetes"를 출력하고 30초간 대기한다.
CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: example-cronjob
spec:
schedule: "0 0 * * *" # 매일 자정에 실행
jobTemplate:
spec:
template:
spec:
containers:
- name: busybox
image: busybox
command: ["sh", "-c", "echo 'Hello Kubernetes from CronJob'"]
restartPolicy: Never
이 에제는 바로 위 Job과 비슷하지만 Cron 스케쥴을 추가해 매일 자정에 실행되도록 하였다.
jobTemplate은 실행될 작업의 템플릿을 정의한다. 지금까지 위 예제들에서 본 Template과 형식은 동일하다.
7. Service
Service는 쿠버네티스에서 클러스터 내의 여러 파드들에 대해 네트워크 접근을 제공하는 추상화 레이어라고 생각한다. 클러스터 내부 뿐 아니라 외부에서 파드에 접근할 수 있는 네트워크 주소를 제공하고 클러스터 내 파드들이 동적으로 생성, 수정, 삭제되더라도 일관된 접근을 보장한다. 일관된 접근이라는 것은 파드가 삭제되거나 생성되면 IP가 바뀌지만 Service는 고정된 DNS 이름을 제공한다는 뜻이다.
또한 파드의 Replicas가 여러 개인 경우 하나의 서비스가 여러 파드를 대상으로 로드 밸런싱 기술도 지원한다.
서비스에 대해서는 해당 문서를 꼭 읽어보는 것이 좋을 것 같아서 주소를 공유해 놓도록 하겠다.
https://kubernetes.io/ko/docs/concepts/services-networking/service/
Service에는 여러가지 유형이 있다. ClusterIP, NodePort, LoadBalancer, ExternalName 이 네가지 svc(service)를 알아보자.
ClusterIP )
ClusterIP: None으로 설정했을 경우 Headless svc로 설정되었다. 위에서 설명한 StatefulSet을 참고하자.
따로 타입을 명시하지 않으면 기본 값이 ClusterIP 타입이다. ClusterIP는 클러스터 내부에서만 접근 가능한 가상의 IP를 제공한다. 따라서 클러스터 내의 다른 파드들이 해당 서비스를 통해 서로 통신할 수 있다.
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: my-app # 이 서비스가 라우팅할 파드를 선택하는 라벨
ports:
- protocol: TCP
port: 80 # 서비스에 접근할 포트
targetPort: 8080 # 파드가 사용하는 포트
type: ClusterIP # 기본 값, 클러스터 내부에서만 접근 가능
pod A는 동일한 네임스페이스에서 호출하면 my-service 서비스 주소를 DNS로 my-app 컨테이너를 호출할 수 있게 된다. 다른 네임스페이스라면 <서비스이름>.<네임스페이스>.svc.cluster.local 주소로 호출하면 된다.
NodePort )
각 노드에서 고정된 포트를 열어 외부에서 클러스터 내부의 Service(ClusterIP)에 접근할 수 있게 한다.
클러스터 외부에서 트래픽을 받을때 사용되며 모든 노드(워커노드)에서 동일한 포트를 통해 Service에 접근할 수 있다.
이런 느낌으로 보면 이해가 수월할 것 같다. 어떤 노드이든 동일한 노드 포트를 통해 클러스터 내부 svc를 찾게 되고 svc는 파드에게 트래픽을 로드밸런싱한다.
apiVersion: v1
kind: Service
metadata:
name: my-service-nodeport
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
nodePort: 30001 # 노드에서 접근할 외부 포트
type: NodePort
nodePort를 30001로 지정했으니 어떤 노드(워커노드)의 30001로 접근을 하여도 해당 pod의 8080 포트로 접근할 수 있게 된다.
LoadBalancer )
LoadBalancer 타입은 클라우드에서 로드 밸런서를 사용하여 외부에서 접근할 때 자동으로 로드밸런서를 생성하고 외부에서 접근할 수 있게 하기 위한 오브젝트이다. AWS에서 해당 LoadBalancer를 사용하면 CLB(Classic LoadBalancer)가 생성된다. (CLB는 잘 쓰이지 않기 때문에 Ingress를 활용하여 ALB나 NLB를 생성하여 사용한다. Ingress는 8번에서 알아보자.)
apiVersion: v1
kind: Service
metadata:
name: my-service-loadbalancer
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer # 클라우드 환경에서 로드밸런서 사용
ExternalName )
ExternalName은 클러스터 내부에서 외부 DNS 이름을 가리키는 svc를 만든다.
예시를 들면 이해가 쉬울 것이다.
apiVersion: v1
kind: Service
metadata:
name: my-external-service
spec:
type: ExternalName
externalName: example.com # 외부 DNS 이름
클러스터 내부의 애플리케이션은 my-external-service를 통해 example.com으로 접근할 수 있다.
8. Ingress
Ingress는 쿠버네티스 클러스터 내의 HTTP, HTTPS 트래픽을 관리하는 리소스이다.
Ingress는 외부에서 클러스터 내의 서비스로 HTTP 요청을 라우팅하는 역할을 한다. Service와 역할은 비슷한 것 같은데 따로 분리해둔 이유는 잘 모르겠다. 다만 Service는 쿠버네티스 클러스터 내부 통신을 위한 주소이지만 외부에서 접근 가능하도록 기능을 추가한 느낌이라면 Ingress는 정말 외부의 트래픽을 쿠버네티스 클러스터안으로 유입하기 위해 만들어진 오브젝트 같다고 생각이 든다.
기본적으로 SSL/TLS 종료를 지원하여 보안 연결을 처리하고 내부 서비스와의 통신은 평문으로 할 수 있다고 한다. 또한 로드 밸러서 역할도 수행하여 여러 파드에 분산하여 트래픽을 처리할 수 있다.
이 또한 예시를 보는게 가장 적합할 것 같다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
spec:
rules:
- host: example.com # 요청이 들어올 호스트 이름
http:
paths:
- path: /path # 요청 경로
pathType: Prefix
backend:
service:
name: example-service # 해당 경로로 트래픽을 라우팅할 서비스 이름
port:
number: 80 # 서비스의 포트 번호
example.com으로 요청이 들어오는데 path가 /path라면 클러스터 내부의 example-service로 라우팅된다.
Ingress 리소스는 단독으로 동작하지 않으며 실제로 트래픽을 처리하는 역할은 Ingress Controller가 맡는다. Ingress Controller는 클러스터 내에서 Ingress 리소스를 모니터링하고 해당 리소스에 맞게 외부에서의 요청을 처리한다.
Ingress Controller는 Nginx Ingress Controller, Traefik Ingress, AWS ALB Ingress Controller 등이 있다. EKS를 쓴다면 AWS 환경에서 ALB(Application LoadBalancer)를 사용하여 Ingress를 처리한다.
AWS ALB Ingress Controller를 활용하면 타겟그룹은 자동으로 생성되고 리스닝 규칙에 http host 헤더는 example.com, path는 /path로 rule이 생성될 것이다. 물론 로드밸런서가 없다면 로드밸런서는 생성된다. ALB or NLB 설정을 포함하여 AWS Ingress Controller를 사용하면 애너테이션을 통해 여러 로드 밸런서의 동작을 더욱 설정할 수 있다.
예시만 잠깐 보고 가자.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
namespace: default
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing (외부 노출 로드밸런서)
alb.ingress.kubernetes.io/target-type: ip (ClusterIP 연결)
alb.ingress.kubernetes.io/group.name: 로드밸런서 그룹핑
alb.ingress.kubernetes.io/load-balancer-name: 로드밸런서 이름
alb.ingress.kubernetes.io/certificate-arn: "acm arn"
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
alb.ingress.kubernetes.io/ssl-redirect: '443'
alb.ingress.kubernetes.io/healthcheck-path: "헬스체크 경로"
alb.ingress.kubernetes.io/security-groups: "보안그룹 sg명"
alb.ingress.kubernetes.io/manage-backend-security-group-rules: "true"
alb.ingress.kubernetes.io/ssl-policy: "ELBSecurityPolicy-TLS13-1-2-2021-06"
spec:
ingressClassName: alb or nlb
rules:
다음과 같이 자동 프로비저닝 되는 로드밸런서의 설정을 애노테이션으로 컨트롤 할 수 있다.
이렇게 다양한 Ingress Controller를 통해서 외부의 트래픽을 쿠버네티스 클러스터 안으로 유입시킬 수 있다.
9. ConfigMap
ConfigMap은 쿠버네티스에서 애플리케이션에 필요한 구성 데이터를 관리하기 위한 오브젝트이다. 애플리케이션의 설정 정보를 환경 변수나 파일 형식으로 Pod에 전달할 수 있는데, 이를 통해 코드 수정 없이 환경 설정을 변경할 수 있어 유연한 애플리케이션 배포가 가능하다.
Devops 직무 쪽을 걸으면서 12 factor에 많이 의존하게 됐는데 하기 링크에 (3)설정과 (10)개발/프로덕션 환경 일치에서 말하는 것이 이 환경 변수를 어떻게 관리하느냐가 핵심이 될 것 같다.
12 factor는 가볍지만 묵직한 느낌을 항상 나에게 가져다준다. 아직 본 적이 없다면 참고하길 바란다.
다시 본론으로 돌아와 ConfigMap은 이러한 환경 변수를 포함한 데이터를 관리하기 위한 오브젝트이다.
이는 키, 값으로 관리가 되는데 간단한 예시를 보면 이해에 도움이 될 것이다.
apiVersion: v1
kind: ConfigMap
metadata:
name: my-config
data:
app.name: "MyApp"
app.version: "1.0"
log.level: "DEBUG"
ConfigMap 오브젝트를 정의하였고 data에 app.name, app.version, log.level을 key값으로 정의하였고 "MyApp", "1.0", "DEBUG"를 value로 정의하였다. 이렇게 정의된 값을 다음처럼 활용할 수 있다.
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: my-container
image: nginx
env:
- name: APP_NAME
valueFrom:
configMapKeyRef:
name: my-config
key: app.name
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: my-config
key: log.level
env에 APP_NAME은 "MyApp", Log_LEVEL은 "DEBUG"값이 바인딩된다.
실제로 필자는 자바/스프링으로 된 서비스에서 JAVA_OPS 같은 값들로 프로필(dev, prod)마다 다른 변수 값들이 주기 때문에 JAVA_OPS를 ConfigMap으로 활용하였다.
다음 처럼 prod-config-exam을 정의하고 env에 주입해주면 JAVA_OPS를 하나의 오브젝트로 분리할 수 있다.
apiVersion: v1
kind: ConfigMap
metadata:
name: prod-config-exam
namespace: innog-prod
data:
JAVA_OPTS: >-
-Xms3G
-Xmx3G
-XX:+UseG1GC
-XX:+UseStringDeduplication
-XX:MaxMetaspaceSize=512M
-XX:+UnlockExperimentalVMOptions
-XX:G1NewSizePercent=20
-XX:G1MaxNewSizePercent=25
-Dspring.profiles.active=prod
-Dbytebuddy=Y
env:
- name: JAVA_OPTS
valueFrom:
configMapKeyRef:
name: prod-config-exam
key: JAVA_OPTS
이로써 설정 정보를 쿠버네티스 리소스로 분리하여 유연한 설계를 할 수 있다.
이와 비슷한 Secret이라는 오브젝트가 있는데 이어서 확인해보도록 하자.
10. Secret
Secret은 쿠버네티스에서 민감한 정보를 안전하게 관리하기 위한 오브젝트이다. 패스워드, 인증 토큰, API 키 등의 민감한 정보를 외부 파일에 하드코딩하지 않고 관리할 수 있는데 기본 인코딩은 가장 일반적인 Opaque시크릿을 활용하여 Base64로 인코딩되어 저장된다. 바로 위 ConfigMap과 유사한 형식이기 때문에 간단한 예제를 보고 넘어가자.
다음과 같이 yaml 파일을 만들어서 생성하거나,
apiVersion: v1
kind: Secret
metadata:
name: my-secret
type: Opaque
data:
username: bXlVc2VybmFtZQ== # Base64 인코딩된 값 "myUsername"
password: bXlQYXNzd29yZA== # Base64 인코딩된 값 "myPassword"
명령어로 생성해도 된다. generic 명령을 사용하여 Opaque 유형의 Secret을 만들어보자.
kubectl create secret generic my-secret --from-literal=username=myUsername --from-literal=password=myPassword
kubectl 명령어로 생성하였을 경우 Base64로 인코딩되어 들어간 것을 확인할 수 있었다.
바인딩 되어 사용하는 것은 ConfigMap과 유사하고 secretKeyRef만 확인하도록 하자.
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: my-container
image: nginx
env:
- name: USERNAME
valueFrom:
secretKeyRef:
name: my-secret
key: username
- name: PASSWORD
valueFrom:
secretKeyRef:
name: my-secret
key: password
볼륨을 마운트하여 사용하는 것도 가능하다. Secret에 포함된 각 키는 /etc/secret 디렉토리에 생성된다.
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: my-container
image: nginx
volumeMounts:
- name: secret-volume
mountPath: "/etc/secret"
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: my-secret
파드 생성 후 파드에 접근하여 실제 해당 경로에 값이 존재하는지 확인해보면, secret 값이 잘 마운트 된 것을 확인할 수 있다.
Secret을 더 우아하게 사용해본 경험이 아직 없어 학습이 더 필요할 것 같다. 일단 기본적인 사용 법을 기록해보았고 상황에 따라 ConfigMap과 Secret을 선택하여 활용하면 될 것 같다. 민감하지 않은 일반 설정 데이터는 ConfigMap을 활용하고 민감한 정보를 저장하여 사용할 때는 Secret을 사용하면 될 것 같다. Base64 인코딩만으로는 보안성을 기대하기는 어려울 것 같고 추가적인 암호화도 고려하도록 하자. Base64는 단순 인코딩 방식이기 때문이다. 기본적으로 Secret 값을 etcd에 저장하는데, Base64 인코딩을 사용하기에 etcd에 접근권한이 있다면 Secret을 읽는 게 어려운 일이 아닌 것 같다.
따라서 AWS와 같은 클라우드 서비스를 사용하는 경우 KMS 등을 통해 암호화를 거칠 수 있도록 추가적인 방법을 제공한다고 하니 사용할 때 참고하여 사용하면 될 것 같다.
또한 Secret을 사용할 때는 RBAC(Role-Base Access Control) 정책을 통해 권한에 대한 사용자 접근을 제한하는 것도 정책을 세우고 제한하는 것이 필수라고 생각된다.
11. PV( PersistentVolume) & PVC(PersistentVolumeClaim) & StorageClass & Dynamic Provisioning
PV는 쿠버네티스 클러스터 내에서 스토리지를 제공하는 실제 리소스이다.
스토리지 프로비저너에 의해 클러스터 내에서 생성되며 NFS, AWS EBS, Ceph 등 다양한 유형의 스토리지로 구성할 수 있다. 일단 생성되면 쿠버네티스 클러스터 전체에서 사용 가능하며 특정 PVC 요청에 바인딩될 수 있다. NS(namespace)에 종속되지 않는다는 뜻이다.
쉽게 말해서 PV를 생성하고 PVC로 요청하여 스토리지를 사용한다고 보면 될 것 같다.
AWS EBS를 사용하는 PV의 예제를 보자.
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-1
spec:
capacity:
storage: 8Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
csi:
driver: ebs.csi.aws.com
volumeHandle: vol-xxxx
volumeAttributes:
fsType: ext4
persistentVolumeReclaimPolicy: Retain은 이 PV를 사용하는 PVC가 삭제되더라도 PV가 삭제되지 않도록 수명 주기 정책을 지정하였다.
storageClassName: ""은 이어 살펴볼 내용이지만 스토리지 클래스 없이 수동으로 생성된 PV다. storageClassName이 없는 PVC 요청과 바인딩 될 수 있는데 동적 프로비저닝이 필요한 경우 스토리지 클래스를 사용한다. 쉽게 설명하자면 스토리지 클래스를 활용하면 PV를 수동으로 만들어 놓지 않아도 자동으로 필요한 만큼 PVC를 만들어 스토리지를 활용할 수 있다. 이 부분은 동적 프로비저닝에서 다시 살펴보고 넘어가자.
CSI(Container Storage Interface)를 사용하여 PV를 정의하였다. CSI는 쿠버네티스에서 외부 스토리지 프로바이더와 통합하기 위한 인터페이스로 생각하면 되는데 현재 EBS 볼륨 ID와 EBS 볼륨 속성을 정의하였다.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-1
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 8Gi
storageClassName: ""
위와 같이 PVC를 생성하면 기존 PV에 스토리지를 요청하게 된다. 실제 파드에서 이를 활용하려면 PVC를 파드에 마운트하여야 한다. 예제 코드를 보면 이해가 쉬울 것이다.
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: my-container
image: nginx
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: storage
volumes:
- name: storage
persistentVolumeClaim:
claimName: pvc-1
이렇게 PV와 PVC를 만들어 파드에서 데이터를 저장하기 위한 영구 저장소로 AWS EBS 볼륨을 활용하였다.
아까 설명하다만 storageClassName와 동적 프로비저닝(Dynamic Provisioning)에 대해 마저 설명하자면, 동적 프로비저닝은 PV를 자동으로 생성하는 방법이다. 사용자가 PVC를 요청할 때 적절한 PV가 존재하지 않으면 동적으로 PV를 생성하여 PVC에 바인딩해주는 방식이다. 이 과정에서 쿠버네티스는 StorageClass에 정의된 규칙을 따른다. 순서는 이렇게 정리하면 될 것 같다.
- 사용자가 PVC를 생성할 때, storageClassName을 지정한다.
- Kubernetes는 요청된 스토리지 클래스에 맞는 StorageClass 객체를 찾는다.
- 해당 StorageClass 객체에 정의된 Provisioner(예: AWS EBS, GCE Persistent Disk, Ceph 등)가 동적으로 PV를 생성한다.
- 생성된 PV는 PVC와 자동으로 바인딩되며, 사용자가 원하는 스토리지를 사용할 수 있게 된다.
다음처럼 StorageClass를 정의하고 PVC에서 StorageClass를 설정하여 동적으로 EBS 볼륨을 생성하도록 요청하면 된다.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ebs-sc
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp2
fsType: ext4
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-2
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 8Gi
storageClassName: ebs-sc
저장소 용량을 동적으로 관리하기 위해 사용하면 좋을 거 같다. 필요한 만큼 사용하기 때문에 자원의 소비를 최적화하고 필요할 때마다 리소스를 할당할 수 있지 않을까 생각된다. 또는 다양한 스토리지 제공자를 사용할 때 활용할 수 있을 거 같다. 여러 스토리지 백엔드(AWS EBS, NFS 등)를 사용하는 경우 스토리지 제공자가 다를 때마다 PV를 수동으로 만들 필요가 없기 때문이다. 대신 적절한 StroageClass와 Provisioner의 설정이 필요하다.
12. ServiceAccount & Role & RoleBinding
쿠버네티스 클러스터에 다른 사용자가 접근하는 것에 대해서 role을 지정하여 권한을 관리할 수 있다. 여기서 말하는 사용자는 개발자가 될 수도 있고 다른 관리자가 될 수도 있지만 특정 파드같은 서비스가 될 수 있다. 특히 파드같은 경우 ServiceAccount 리소스를 만들고 파드가 해당 ServiceAccount를 사용한다고 명시적으로 지정해주어야한다. 그런 다음 어떤 권한을 가질지 Role을 만들어주고 RoleBinding을 통해 해당 Role을 어떤 사용자 또는 ServiceAccount에게 바인딩할 수 있다.
간단한 예제 코드로 살펴보자.
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-service-account
my-service-account라는 ServiceAccount를 만들었다. 이제 이것을 파드에게 입혀보자.
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
serviceAccountName: my-service-account
containers:
- name: my-container
image: my-image
my-pod라는 파드는 my-service-account라는 계정을 가지게 되었다.
이 계정이 어떤 권한을 가질지 정의하는 Role을 간단히 만들어보자. 참고로 Role은 네임스페이스 내에서만 유효하며, 클러스터 전체에 적용되는 권한을 설정하려면 ClusterRole을 사용해야 한다. ClusterRole은 13번에서 다시 확인하도록 하자.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
이 Role은 default 네임스페이스 내에서 pods 리소스에 대해 get, list, watch 권한을 부여한다.
Role을 생성하였으니 위에서 만든 pod에게 (정확히는 pod의 ServiceAccount인 my-service-account) Role을 Binding한다.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
- kind: ServiceAccount
name: my-service-account
namespace: default
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
이런 방식으로 파드에게 권한을 부여할 수 있고 만약에 EKS를 사용한다면 IAM 사용자 또는 Role을 활용하여 그룹을 만들어 권한을 부여할 수도 있다.
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: read-pods
namespace: default
subjects:
- kind: Group
name: developer
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
subjects에 ServiceAccount가 아닌 Group를 지정하였고 이는 aws-auth config에 정의되어있다.
kubectl edit -n kube-system configmap/aws-auth
apiVersion: v1
data:
<role로 추가 시>
mapRoles: |
- groups:
- developer
rolearn: arn:aws:iam::<ACCOUNT_ID>:role/<IAM_ROLE_NAME>
username: <IAM_ROLE_NAME>
<user로 추가 시>
mapUsers: |
- userarn: arn:aws:iam::<ACCOUNT_ID>:user/<IAM_USER_NAME>
username: <IAM_USER_NAME>
groups:
- developer
kind: ConfigMap
위와 같이 Group을 만들어 IAM User&Role에 쿠버네티스 Role을 RoleBinding을 해줄 수 있다.
이러한 구조로 세분화된 권한 관리가 가능하며 특정 파드가 불필요한 리소스에 접근하는 것도 제한할 수 있다.
잘 활용하지 못하고 있는 오브젝트 중 하나이지만 네임스페이스 별로 권한을 제한하여 보안을 강화하고 특정 파드에만 필요한 권한을 부여하고자 할 때 유용하게 사용할 수 있을 거 같다. 아까 잠시 설명헀지만 Role은 해당 네임스페이스에서만 가능하다. 클러스터 전체에 활용하기 위해서는 ClusterRole을 만들어야한다.
13. ClusterRole & ClusterRoleBinding
CluterRole과 ClusterRoleBinding은 쿠버네티스에서 클러스터 수준의 접근 제어와 권한 관리를 담당하는 RBAC 구성 요소이다.
위에서 살펴본 Role과 RoleBinding과 거의 유사하여 간단하게 보고 넘어가자.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: manage-deployments
rules:
- apiGroups: ["apps"] # "apps" API 그룹의 리소스 권한 부여
resources: ["deployments"] # apps API 그룹 내의 "deployments" 리소스를 지정
verbs: ["get", "list", "create", "delete"]
- apiGroups: [""] # Core API 그룹의 리소스 권한 부여 (빈 문자열로 지정)
resources: ["pods"]
verbs: ["get", "list"]
apiGroups 필드는 어떤 API 그룹의 리소스를 대상으로 권한을 설정할지 지정하는 역할을 한다.
Kubernetes의 API 리소스들은 여러 API 그룹에 나누어져 있으며, API 그룹에 따라 다르게 접근하도록 정의할 수 있.
- "" (빈 문자열) : Core API 그룹을 나타내며, pods, services, configmaps와 같은 기본 리소스가 여기 속한다.
- "apps" : deployments, statefulsets, daemonsets 같은 리소스가 포함된다.
해당 CluterRole을 바인딩하기 위한 ClusterRoleBinding은 RoleBinding과 유사하다.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: manage-deployments-rolebinding
subjects:
- kind: ServiceAccount
name: my-service-account
namespace: default
roleRef:
kind: ClusterRole
name: manage-deployments
apiGroup: rbac.authorization.k8s.io
클러스터 관리자 또는 클러스터 전체에 접근 가능해야하는 경우 활용될 수 있다. 바로 생각나는건 로깅이나 모니터링 시스템인 것 같다. 모든 네임스페이스에 걸쳐 특정 리소스들에 접근이 필요하기 때문에 ClusterRole이 활용될 것으로 예상된다. 실제로 조회해보니 fluent-bit, prometheus, kiali 등 모두 ClusterRole을 사용하는 것을 확인할 수 있었다.
이외에도 다양한 ClusterRole이 있었다. 쿠버네티스 권한 관리는 유연하고 보안성이 뛰어나보이지만 구성의 복잡성이 꽤 높다고 느꼈다. 작은 팀에서는 부담스러울 수 있지만 RBAC을 통해 매우 세밀하게 권한을 설정할 수 있어 쿠버네티스 클러스터 환경에서 여러 사용자와 서비스(애플리케이션)가 공존할 때 보안과 운영 효율성을 높여주는 것 같다.
14. HPA & VPA & CA
Horizontal Pod Autoscaler (HPA)와 Vertical Pod Autoscaler (VPA)은 말 그대로 파드의 확장을 수평적으로 할 것인지 수직적으로 할 것인지를 말하고 특정 리소스의 부족 시 파드를 증가시켜주는 역할을 한다. 예를 들어 CPU 사용률이 60이 넘어가면 Pod 수를 증가시킬 수 있고, 또는 CPU나 메모리 리소스를 조정하여 수직적으로 확장시킨다. 스케일 업이나 스케일 아웃 등의 확장이 필요한 환경이 아니라면 HPA & VPA를 설정할 필요는 없다.
참고로 HPA & VPA 중 하나만 적용 가능하다고 한다.
HPA부터 예제를 만들어 확인해보자.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-hpa
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-deployment
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # 스케일 다운 안정화 시간 (5분)
policies:
- type: Percent
value: 100
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 30
policies:
- type: Percent
value: 50 # CPU 사용률 기준으로 15초 동안 100%까지 증가 가능
periodSeconds: 15
- type: Pods
value: 2 # 15초 동안 최대 4개의 Pod까지 증가 가능
periodSeconds: 15
selectPolicy: Max # 가장 큰 스케일 아웃 옵션을 선택
위 코드에서 my-deployment를 HPA가 스케일링할 대상으로 지정하였으며 최소 파드 수를 2개, 최대 파드 수를 10개로 제한하였다.
스케일 아웃 규칙은 평균 CPU 사용률이 70%를 초과하면 스케일 아웃 되도록 설정하였다. behavior 필드에서 스케일 업과 스케일 다운 동작을 세밀하게 조정할 수 있다.
파드를 줄일 때 안정화 시간을 30초로 설정하였고 15초마다 Pod를 증가시키는데 증가시키는 Pod 갯수는 두가지 정책 중 Max값을 선택하였다.
apiVersion: "autoscaling.k8s.io/v1beta2"
kind: VerticalPodAutoscaler
metadata:
name: my-vpa
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: my-deployment
updatePolicy:
updateMode: "Auto" # 리소스 요구 사항이 변경될 때 자동으로 리소스를 조정
resourcePolicy:
containerPolicies:
- containerName: '*'
minAllowed:
cpu: 100m
memory: 50Mi
maxAllowed:
cpu: 2
memory: 500Mi
controlledResources: ["cpu", "memory"]
VPA는 현재 파드의 리소스 사용량을 분석하여 리소스가 부족하거나 과도하게 할당된 경우 적절한 리소스를 할당한다고 한다.
위와 동일하게 my-deployment 를 대상으로 지정하였으며 최소 및 최대 리소스를 지정하였다.
kubectl describe vpa hamster-vpa
해당 describe 명령어를 실행하면 vpa에 대한 자세한 정보가 나오는데 vpa는 이 값을 보고 pod의 리소스 크기를 수정한다.
실제로 pod의 크기를 조정하지 않고 추천해주는 리소스만 확인하려면 updatePolicy: "Maual"을 주면 된다. auto가 기본 값이다.
참고로 HPA는 쿠버네티스 기본 리소스이고 VPA는 CRD를 통해 추가해야하는 커스텀 리소스이다. EKS를 사용 중이라면 하기 링크를 참고해 설치가 필요하다.
https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/vertical-pod-autoscaler.html
HPA는 각 파드의 리소스 메트릭을 읽을 수 있어야 하기에, Metric Server 정도 설치했던 기억이 있다.
https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
metric은 pull 방식으로 15초마다 한번씩 갱신된다.
마지막으로 CA(Cluster Autoscaler)는 쿠버네티스 클러스터의 노드 수를 자동으로 조정하여 클러스터에 실행되는 워크로드들이 요구하는 자원을 충족할 수 있게 해주는 컴포넌트이다.
HPA와 VPA는 파드레벨의 리소스 최적화에 중심이면 CA는 클러스터 수준의 노드 리소스를 관리한다. 쉽게 설명하자면 파드가 올라갈 노드의 자원이 없으면 노드를 스케일 아웃하는 것을 의미한다.
EKS를 사용하는 경우 ClusterAutoscaler는 노드 그룹에 적용된 최대 크기만큼 scale out이 되며, 리소스 사용량이 적은 노드를 최소 크기까지 scale in 하여 비용적인 부담을 덜 수 있다.
cluster-autoscaler-autodiscover.yaml을 다운로드하고 부분만 클러스터 명으로 변경하면 된다. Cluster Autoscaler는 오토스케일링 k8s.io/cluster-autoscaler/enabled, k8s.io/cluster-autoscaler/ Tag를 기반으로 'Desired capacity'를 조정한다. 오토 스케일링에 해당 Tag가 적용되어 있지 않다면 적용해야한다.
15. PodDisruptionBudget
PDB(PodDisruptionBudget)은 쿠버네티스에서 애플리케이션의 가용성을 유지하기 위해 특정 파드 수가 항상 유지되도록 설정하는 정책이다. 장애 발생 시나 유지 보수(업그레이드) 작업할 때 최소 파드 수를 확보하여 서비스가 중단되는 것을 방지한다.
maxUnavailable(최대 허용 중단 수)와 minAvailable(최소 가용 파드 수)로 설정 가능하다.
간단한 설정으로 정의할 수 있기에 예시만 빠르게 보고 넘어가도록 하자.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: nginx-pdb
namespace: default
spec:
minAvailable: 1 # 최소 1개의 파드가 가용 상태로 있어야 함
selector:
matchLabels:
app: nginx
보통 클러스터 노드 업그레이드 작업이 있는 경우 노드 업그레이드 중 파드가 안전하게 중단되고 가용성이 유지될 것이고 예상치 못한 장애를 대비할 수 있을 것으로 생각된다. 서비스의 안정성을 보장하는 중요한 도구이므로 필수로 생각된다.
그 외 LimitRanges & ResourceQuotas & NetworkPolicy 이런 정책들도 있다.
특정 네임스페이스의 내 모든 컨테이너의 리소스의 범위를 제한하거나, (파드안에 reqeust, limit과 다른 개념)
네임스페이스 전체에서 사용할 수 있는 자원을 설정하여 특정 네임스페이스가 자원을 독점하는 것을 방지할 수 있다. 또한 파드의 네트워크 트래픽을 세밀하게 제어하여 보안을 강화할 수도 있다. 이 부분은 아직 활용해보지 못했기에 일단 개념만 이해하고 넘어가도록 했다.
16. CR(Custorm Resource) & CRD(CustomResourceDefinition)
지금까지 위에서 살펴본 기본적인 리소스 이외의 추가적인 사용자 정의 리소스를 만들 수 있다.
이때 사용되는 것이 CR, CRD이다.
예를 들어 Mysql이라는 애플리케이션을 위한 리소스를 정의하고자 한다면 Mysql이라는 CRD를 만들어 쿠버네티스가 인식할 수 있도록 리소스를 정의하고 그 후 Mysql 리소스를 CR로 만들어 클러스터 내에서 생성 및 관리할 수 있도록 한다.
CRD는 단지 정의하는 것이고 CR을 생성하면 CRD를 기반으로 실제 리소스가 생성되는 것이다.
CR, CRD 및 컨트롤러까지 직접 만들어 사용할 환경은 아니였지만 카프카, 프로메테우스 등 Operator 패턴을 활용하고 있었고 이는 CRD, CR로 리소스를 관리하고 있었기에 무슨 개념인지는 알아둬야한다 생각하였다.
간단한 예제만 살펴보고 필요 시 추가적으로 찾아보도록 하자.
이건 GPT가 만들어준 예제코드이다. 잘 돌아갈 지는 모르겠다. 대충 CR, CRD가 무엇인지 감만 잡기 위해 참고용도로 활용하였다.
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: mysqls.example.com
spec:
group: example.com # API 그룹 이름
names:
plural: mysqls # 리소스의 복수형 이름
singular: mysql # 리소스의 단수형 이름
kind: MySQL # CR의 Kind
shortNames:
- mydb # 짧은 이름으로 'mydb'로 사용 가능
scope: Namespaced # 네임스페이스 범위
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
replicas:
type: integer
description: "Number of MySQL instances"
version:
type: string
description: "MySQL version"
storageSize:
type: string
description: "Storage size for MySQL data (e.g., 5Gi)"
rootPassword:
type: string
description: "Root password for MySQL"
위 처럼 CRD를 정의해놓는다. 프로퍼티 값이 이제 CR로 선언할 때 포함되는 값으로 보인다.
apiVersion: example.com/v1
kind: MySQL
metadata:
name: my-mysql-instance
namespace: default
spec:
replicas: 1 # MySQL 인스턴스 개수
version: "8.0" # MySQL 버전
storageSize: "5Gi" # 스토리지 크기
rootPassword: "my-secret-password" # MySQL root 비밀번호
Mysql CRD를 기반으로 CR을 생성하였다.
그런데 여기서 궁금한 건 실제로 Mysql 인스턴스를 생성하려면 어떻게 해야할 지 의문점이 들었다. 쿠버네티스는 etcd에 저장된 값을 컨트롤러가 계속 확인하고 이 값을 맞추기 위해 동작한다. 이와 마찬가지로 찾아보니 커스텀 컨트롤러 혹은 Operator가 이 CRD를 감시하고 사용자가 CR을 생성하면 실제 서비스 등을 생성하는 역할을 한다고 한다.
쿠버네티스 아키텍쳐와 동일하다. Mysql CR이 생성되면 컨트롤러가 이를 감지 -> CR의 스펙을 확인한 후 지정된 (변수)값으로 서비스를 생성한다. -> CR이 업데이트되거나 삭제되면 컨트롤러가 이를 감지하여 리소스를 업데이트하거나 삭제한다.
client-go, controller-runtime 같은 라이브러리로 Controller를 개발하거나 Operator SDK를 사용하여 개발할 수 있다고 한다.
직접 소스를 구현하지는 않았지만 대충 이런 흐름의 로직을 가질 거라고 예상된다.
1. Mysql CR 생성 감지 -> 2. CR 정보 가져오기 -> 3. Mysql Deployment & Service 생성
이런 로직을 가진 서비스를 4. 쿠버네티스 클러스터에 배포하면 되지 않을까 싶다.
간단 Operator 패턴 설명은 하기 글을 참고하도록 하자.
https://sh970901.tistory.com/161
지금까지 16가지 혹은 그 이상의 쿠버네티스 워크로드, 구성 및 암호, 서비스 및 네트워킹, 스토리지 등과 관련된 리소스(오브젝트) 들을 간략하게 살펴보았다. 간단하게라도 글로 정리해보면서 머릿 속에 정리가 된 것 같다.
놓치고 있는 부분도 많고 잘 활용하지 못한 부분, 모르고 사용하며 기능 동작에만 우선을 둔 부분도 꽤 많았다. 하지만 구축 과정이 거의 끝난 시점부터 깊이있이 다시 되돌아보는 것도 좋은 방법인 것 같다. 매일 부족하다고 느끼지만 이전에 설명한 내용, 구축한 내용을 보고 아쉽다는 생각이 들 때마다 한편으로는 그만큼 내가 성장했다고 느껴지는 것 같아 뿌듯한 감정이 동시에 느껴진다.
'IT' 카테고리의 다른 글
EC2 Jar CICD Pipeline (GitLab Runner, AWS CodeDeploy) (6) | 2024.11.13 |
---|---|
쿠버네티스[EKS] CICD review (ArgoCD+GitLab Runner) (0) | 2024.11.12 |
쿠버네티스 아키텍쳐 & 컴포넌트 (3) | 2024.11.06 |
쿠버네티스 Operator Pattern (2) | 2024.10.29 |
쿠버네티스 환경 JAVA heap 참고 글 정리 (5) | 2024.10.29 |