쿠버네티스가 조금 약한 거 같아서 최근 강의를 하나 들었고,
해당 내용을 좀 길지만 머릿속으로 정리해볼 겸 작성해 본 글입니다.
컨테이너를 돌리기 위해서는 하나의 서버와 컨테이너 런타임이 있어야 한다.
하지만 가용성과 확장성 측면 때문에 결국은 이중화를 진행해야 된다.
이런 이중화된 서버에 실행될 컨테이너들을 관리하기 위한
자동화된 소프트웨어가 필요하게 됐고, 그게 바로 쿠버네티스(kubernetes)다.
Kubernetes(쿠버네티스) 는 컨테이너 오케스트레이션 소프트웨어이다.
물리적으로 분리된 여러 서버와 그 위에서 실행될 여러 컨테이너들을
일괄적으로 관리하는 게 핵심 목표이다.
참고: Kubernetes 는 k8s 로 줄여서 부르기도 한다.
이하에서는 모두 k8s 로 줄여 부르겠다.
k8s 는 여러 서버 에 컨테이너 동작할 컨테이너들을 관리/조율하는게 목표이다.
이때 이 서버들을 묶어서 Cluster (클러스터) 라고 표현한다.
이렇게 하나로 묶음으로써 통합 관리가 가능케 한다.
그리고 각각의 서버는 또 노드(Node)라고 표현한다.
사용자는 원하는 상태를 정의 하여 k8s 를 건내주고,
k8s 는 이 상태에 맞게 동작되도록 지속적으로 조정하게 된다.
여기서 말하는 "지속적인 조정" 을 컨트롤 루푸(Control Loop) 라고 한다.
컨트롤 루프는 크게 3가지 절차로 이루어진다.
관찰 한다. (=정보수집)비교 한다.조치 한다.즉 관찰, 비교, 조치로 이루어진다.
위에서는 간단하게 "사용자가 정의한 상태"라고 모호하게 표현했지만,
정확히 네트워크, 스토리지, 환경변수 등의 상태를 의미한다.
즉, 상태라는 말은 정확히는 애플리케이션이 실행될 환경의 구성 요소들 을 의미한다.
k8s 에서 리소스란 애플리케이션이 실행될 환경의 구성요소들을
정의하기 위해 사용하는 구성 단위이다.
k8s 에서는 다음과 같은 리소스가 존재한다.
이러한 모든 리소스는 yaml 로 표현이 가능하다.
ex:
apiVersion: v1 # 리소스 버전 (필수값)
kind: Pod # 리소스 타입 명 (필수값)
metadata: # 리소스의 부가 정보
name: my-pod # (리소스 이름, 필수값)
namespace: my-ns # 리소스가 속한 Namespace
labels:
app: my-app # 그룹화를 위한 Label
spec: # 리소스의 속성
(...생략...)
kubectl 은 k8s 를 사용하기 위한 프로그램이고,
이 kubectl 을 통해서 k8s 클러스터와 상호작용을 할 수 있다.
kubectl get pod # 리소스 목록조회
kubetl get pod my-pod -o yaml # 이렇게 하면 특정 리소스 하나에 대해서 yaml 형태로 정보를 조회할 수 있다.
kubectl describe pod my-pod # 리소스 상세 내역
kubectl apply -f my-pod.yaml # yaml 을 통한 리소스 생성 (더 일반적)
kubectl create deployment m-nginx --image=nginx # 명령어를 통한 리소스 생성
kubectl apply -f my-pod.yaml # 리소스 수정, 똑같으면 변경 X
kubectl edit pod my-pod # vim 같은 에디터로 리소스 수정
kubectl delete -f my-pod.yaml # yaml 을 통한 리소스 삭제
kubectl delete pod my-pod # 리소스 타입과 명칭으로 직접 삭제
참고: apply 와 delete 는 yaml 파일이 여러개 있는 디렉토리에도 사용할 수 있다.
ex:kubectl apply -f 디렉토리_경로
ex:kubectl delete -f 디렉토리_경로
이러면 디렉토리에 안에 있던 각각의 yaml apply/delete 된다.
다양한 리소스들을 논리적으로 분리하기 위한 일종의 폴더이다.
NameSpace (=ns) 안에 있는 리소스들은 Namespace 를 삭제하면 한번에 다 같이 지워진다.
apiVersion: v1
kind: Namespace
metadata:
name: my-ns
kubectl get pods -n my-namespace
# 참고로 -n 으로 지정 안하면 default 네임스페이스를 뒤져본다.
# 전체 NameSpace 에 대해서 리소스 조회
kubectl get pods -A
주의:
실제 컨테이너를 실행할 Node 에서는 Pod 라는 개념은 없다! 그냥 컨테이너 밖에 모른다.
Pod 는 k8s 가 컨테이너를 관리하기 위한 논리적인 단위이다!
## 파일명: toast-pod.yaml
# # toast 네임스페이스도 같이 생성
# apiVersion: v1
# kind: Namespace
# metadata:
# name: toast
#---
# Pod 를 생성
apiVerion: v1
kind: Pod
metadata:
name: toast-pod
namespace: toast # 필수는 아님! 지정 안하면 default ns 들어감
spec:
containers:
- name: nginx
image: nginx:alpine-slim
# name, image 는 필수값!
# 위처럼 yaml 로 하는 게 정석이지만, 귀찮다면 bash 에서 명령어로도 할 수 있다.
# kubectl run nginx --image=nginx:alpine-slim -n toast
# POD 생성
kubectl apply -f toast-pod.yaml
# POD 조회
kubectl get pod -n toast
# 상세조회
kubectl describe pod toast-pod -n toast
# yaml 뽑애나기
kubectl get pod -n toast -o yaml
# 로그 조회
kubectl logs -f toast-pod -n toast # ctrl+c 로 탈출 가능
# 삭제
kubectl delete pod toast-pod -n toast
# 가끔은 Pod 를 띄우지는 않지만, 템플릿 yaml 파일이 필요할 때가 있다.
# 이때 쓰면 좋은게 "--dry-run=client" 옵션이다
# 이러면 실제 실행은 안되고, yaml 을 뽑아낼 수 있다.
kubectl run sample-pod --image=nginx:slim-alpine \
--dry-run=client -n toast -o yaml > some-pod.yaml
# 출력 결과:
# apiVersion: v1
# kind: Pod
# metadata:
# labels:
# run: sample-pod
# name: sample-pod
# namespace: toast
# spec:
# containers:
# - image: nginx:alpine-slim
# name: sample-pod
# resources: {}
# dnsPolicy: ClusterFirst
# restartPolicy: Always
# status: {}
참고: kubectl describe 명령어 결과물
Name: toast-pod
Namespace: toast
Priority: 0
Service Account: default
Node: worker-node-3/172.22.0.4
Start Time: Sat, 20 Dec 2025 10:41:04 +0900
Labels: <none>
Annotations: <none>
Status: Running
IP: 10.244.3.2
IPs:
IP: 10.244.3.2
Containers:
nginx:
Container ID: containerd://(...생략...)
Image: nginx:alpine-slim
Image ID: (...생략...)
Port: <none>
Host Port: <none>
... (생략)...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 8m18s default-scheduler Successfully assigned toast/toast-pod to worker-node-3
Normal Pulled 8m17s kubelet Container image "nginx:alpine-slim" already present on machine
Normal Created 8m17s kubelet Created container nginx
Normal Started 8m17s kubelet Started container nginx
7 개가 있다.
# kubectl run some-app ... 명령어 입력 후...
- [Pending]
|
|
V
- [ContainerCreating] --> !! ImagePullBackOff !!
|
|
V
- [Running] --> [Succeeded] (실행됐고, 정상 종료. 주로 1회성 배치 컨테이너에서 활용)
| |
| |
--> [Failed] --> (성공이든 실패든 짧은 시간 내에 지속적으로 반복 실행되면...)
|
|
[CrashLoopBackOff] <------
요약:
Pending : Pod 를 실행할 Node 를 찾는 중이다.Node 를 지정하는 것을 Scheduling 이라고 한다.Pod 가 스케줄링됐다 라고도 표현한다.ContainerCreating : Scheduling 완료, 컨테이너 생성을 위한 시간을 갖는다.Image 다운로드 에러가 발생하면 ImagePullBackOff 상태가 된다.Image 이름 오타, 또는 platform 지원이 안되는 경우 발생Running: 컨테이너 생성이 됐고, 실제 실행을 한 상태이다.재시도 정책 (Restart Policy) : Never, OnFailure, Always 지정 가능Always : Succceed/Failed 뭘로 끝나던 간에 재실행한다. Web 서버에 적합.OnFailure : 오로지 Failed 했을 때만 다시 실행한다.Never : Succceed/Failed 뭘로 끝나던 재실행하지 않는다. Batch 에 적합.SUCCEEDED : 실행된 후 exit code = 0 으로 종료된 상태FAILED : exit code != 0 으로 종료된 상태Pod 의 Restart Policy 에 따라 다시 실행될 수도 있다SUCCEED 든 FAILED 이든 짧은 시간 내에 계속 재시도 되면 CrashLoopBackOff 상태가 된다.SUCCEED 인데 CrashLoopBackOff 가 발생하는게 의아할 수 있다.
이건 1회성 앱을 마치 서버처럼 실행해서 그런 것이다.
이때는restart policy를Never또는OnFailure로 지정하면 된다.
그외에도Job/CronJob으로 실행해도 된다.
Pod 의 이중화, 즉 고가용성을 위한 리소스이고,
사용자의 상태 정의(=yaml)를 통해서 Pod 의 수를 정해진 갯수대로
유지해주는 리소스가 바로 ReplicaSet (=rs) 이다.
Replica 라는 건 복제본을 의미한다. 이 복제본은 각각의 Pod 를 의미한다.
이렇게 복제본 Pod 를 관리한다고 해서 Set 을 붙인다.
Pod 의 수를 정해진 갯수대로 유지해주는 것이 바로 ReplicaSet 의 주요 기능이다.
이를 위해서 rs 는 아래와 같은 동작을 한다.
이때 자기가 관리할 파드를 구분 짓는 방법이 중요한데,
레이블(=label) 을 기준으로 관리대상을 타겟팅한다.
# 파일명: toast-rs.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: toast-rs
spec:
# 유지할 POD 의 갯수 지정
replicas: 3
# 관리 대상 POD 를 지정하기 위한 레이블 설정
selector:
matchLabels:
app: toast-proxy
# ReplicaSet 이 관리할 POD 정의
# 참고로 POD 의 이름은 toast-rs-????? 처럼 자동 생성된다.
template:
metadata:
labels:
app: toast-proxy
spec:
containers:
- name: toast-nginx
image: nginx:alpine-slim
이렇게 지정하고 yaml 을 kubectl apply 하면 다음과 같은 일이 벌어진다.
app: backend label 이 붙은 Pod 갯수 체크kubectl apply -f toast-rs.yaml
replicaset.apps/toast-rs created
ToastBread → kubectl get rs
NAME DESIRED CURRENT READY AGE
toast-rs 3 3 3 4m27s
ToastBread → kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
toast-rs-7jhrz 1/1 Running 0 43s app=toast-proxy
toast-rs-9zb8l 1/1 Running 0 43s app=toast-proxy
toast-rs-zgk29 1/1 Running 0 43s app=toast-proxy
## 자동으로 pod 명이 rs 의 name 을 활용하는 것을 볼 수 있다.
## 레이블 또한 template > metadata > labels 에 지정한 것과 동일하다.
ToastBread → kubectl describe rs toast-rs
Name: toast-rs
Namespace: default
Selector: app=toast-proxy
Labels: <none>
Annotations: <none>
Replicas: 3 current / 3 desired
Pods Status: 3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
Labels: app=toast-proxy
Containers:
toast-nginx: # 앞서 yaml 에 지정했던 container 이름이 보인다.
Image: nginx:alpine-slim
(... 생략 ...)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 5m9s replicaset-controller Created pod: toast-rs-zgk29
Normal SuccessfulCreate 5m9s replicaset-controller Created pod: toast-rs-7jhrz
Normal SuccessfulCreate 5m9s replicaset-controller Created pod: toast-rs-9zb8l
## 참고: replicaSet 갯수 늘리기
## kubectl scale rs toast-rs --scale=5
## 레이블 추가
## kubectl label pod toast-rs-zgk29 creator=something
## 레이블 수정
kubectl label pod toast-rs-zgk29 app=toast-proxy2 --overwrite
kubectl get po,rs --show-labels
pod/toast-rs-9wfgz 1/1 Running 0 5m39s app=toast-proxy
pod/toast-rs-x9glv 1/1 Running 0 5m39s app=toast-proxy
pod/toast-rs-xlvxf 1/1 Running 0 5m39s app=toast-proxy2
pod/toast-rs-zd65r 1/1 Running 0 6s app=toast-proxy
## -> 다시 생성
## 다시 지우기...
kubectl delete -f .\toast-rs.yaml
kubectl delete pod -l app=toast-proxy2
애플리케이션의 버전관리와 배포를 담당하는 리소스이다.
여러개의 ReplicaSet 을 관리하며, 각 ReplicaSet 을 하나의 버전(=Revision)으로 관리한다.
Deployment -> ReplicaSet -> Pod 순으로 계층적 리소스 구조를 갖는다.
ReplicaSet 버전이 여러 개 있으면 가장 최신 버전의 rs 의 Pod 만 남기고, 이전 rs 의 Pod 는 없앤다.
다만 이 과정에서 배포 전략(Strategy)에 따라서 생성/삭제의 방식이 조금씩 다르다.
배포전략은 대표적으로 Recreate 와 RollingUpdate 가 있다.
Recreate 은 그냥 기존 Pod 모두 삭제 후 신규 Pod 를 띄운다. 이러면 무중단 배포가 안된다.
RollingUpdate 는 점진적으로 Pod 를 교체한다.
## 파일명 toast-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: toast-deploy
spec:
# 배포전략 지정 (생략 가능!)
strategy:
type: RollingUpdate # default 임.
rollingUpdate:
maxSurge: 2 # 새로운 버전 배포할 때 한번에 배포하는 Pod 갯수
maxUnavailable: 1 # 기존 Pod 삭제 시 동시 삭제 갯수 지정
# ReplicaSet 정의
replicas: 3
selector:
matchLabels:
app: backend
# Pod 정의
template:
metadata:
labels:
app: backend
spec:
containers:
- name: my-nginx
image: nginx:alpine
# 참고로 Pod 정의가 수정될 때만 새로운 리비전이 생성된다!
# 즉 아래처럼 명령어로 deployment 의 pod 정보를 수정할 때 새로운 리비전이 생성된다.
# kubectl set image deploy my-app nginx=nginx:alpine-slim
# 일반적으로는 이미지의 버전이 바뀌어서 리비전이 생성된다.
ToastBread → kubectl apply -f .\toast-deploy.yaml
deployment.apps/toast-deploy created
# 참고: 명령어로도 생성가능
# kubectl create deployment toast-deploy --image=nginx:alpine-slim --replicas=3
# 참고: replicas 변경방법, 이건 하더라도 리비전 생성 X
# kubectl scale deploy toast-deploy --replicas=5
# ------------------------------------------------
ToastBread → kubectl get deploy,rs,pod --show-labels
NAME READY UP-TO-DATE AVAILABLE AGE LABELS
deployment.apps/toast-deploy 3/3 3 3 74s <none>
NAME DESIRED CURRENT READY AGE LABELS
replicaset.apps/toast-deploy-5ccd56fb5d 3 3 3 74s app=backend,pod-template-hash=5ccd56fb5d
NAME READY STATUS RESTARTS AGE LABELS
pod/toast-deploy-5ccd56fb5d-8lz7t 1/1 Running 0 74s app=backend,pod-template-hash=5ccd56fb5d
pod/toast-deploy-5ccd56fb5d-tt56f 1/1 Running 0 74s app=backend,pod-template-hash=5ccd56fb5d
pod/toast-deploy-5ccd56fb5d-vdkkk 1/1 Running 0 74s app=backend,pod-template-hash=5ccd56fb5d
# 배포 상태 확인 (실시간)
kubectl rollout status deploy 리소스명
# 배포 이력
kubectl rollout history deploy 리소스명
# 변경 이력 기록 (deployment 배포 후에 이렇게 처리하면 더 쉽게 변경 추적이 가능)
kubectl annotate deploy 리소스명 Kubernetes.io/change-cause="변경이력"
# 이전 버전(=revision)으로 롤백, 롤백 대상 리비전이 가장 큰 수의 리비전으로 바뀐다.
# 즉 롤백 된 리비전이 가장 최신 리비전으로 변경되는 것이다.
kubectl rollout undo deploy 리소스명 [--revision=1]
# 참고: 배포 일시 중지
# kubectl rollout pause deploy 리소스명
# 참고: 배포 재개
kubectl rollout resume deploy 리소스명
서비스는 Pod 간에 네트워크를 연결해주는 리소스이다.
이에 대한 이해를 위해서 먼저 이중화화 로드밸런싱을 좀 알아보자.
k8s 는
Service(서비스)가 바로 이 로드밸런서의 역할을 수행한다
Service 는 다음과 같은 특성/기능을 갖는다.
참고:
서비스는 지속적으로 POD 의 IP 를 모니터링하기 때문에 신규로 POD 가 생성되더라도,
그에 맞게 내부적으로 설정이 변경되고 자신의 IP 는 계속해서 동일하게 유지한다.
참고2:
서비스는 Pod 의 레이블(label)을 기준으로 타깃을 잡는다.
apiVersion: v1
kind: Service
metadata:
name: toast-svc
spec:
selector:
app: toast-app # 타겟팅할 Pod 의 레이블
ports:
- name: http
protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP # (기본값). 클러스터 내부에서만 접근 가능한 가상 IP
# 외부에서 접근하려면 Ingress 추가로 필요!
# 외부 → Ingress Controller → ClusterIP Service → Pods
port / targetPort 의 차이점:
Pod(=컨테이너) 의 포트번호 ┌─────────────────┐
│ 다른 Pod │
│ (예: 프론트엔드)│
└────────┬────────┘
│
│ http://toast-service:80 요청
│ # toast-service 는 Service 의 이름이다. 이름으로 통신도 가능하다!
▼
┌────────────────────┐
│ toast-svc │ ← ClusterIP: 10.96.100.50 (가상 IP)
│ (Service) │
└─────────┬──────────┘
│
│ 로드밸런싱
│
┌─────┴─────┬──────────┐
▼ ▼ ▼
┌──────┐ ┌──────┐ ┌──────┐
│ Pod1 │ │ Pod2│ │ Pod3 │ ← app: toast-app
│:8080 │ │:8080│ │:8080 │
└──────┘ └──────┘ └──────┘
명령어로도 생성이 가능하다.
kubectl expose deploy 디플로이먼트명 type=ClusterIP --port=진입포트번호 --target-port=컨테이너포트번호 --name=서비스명
kubectl get svc -n toast -o wide # service 조회
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
toast-svc ClusterIP 10.96.135.168 <none> 80/TCP 5m app=toast-app
kubectl describe svc 이름 -n toast # 서비스 상세조회
Name: toast-svc
Namespace: toast
Labels: <none>
Annotations: <none>
Selector: app=toast-app
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.96.135.168
IPs: 10.96.135.168
Port: <unset> 80/TCP
TargetPort: 8080/TCP
Endpoints: 10.244.1.4:8080,10.244.3.3:8080,10.244.2.4:8080
Session Affinity: None
Internal Traffic Policy: Cluster
Events: <none>
## Service 가 Pod 의 정보를 수집할 때 endpoint 를 통해서 IP 정보를 관리한다.
## endpoint 도 하나의 리소스이기 때문에 조회가 된다.
kubectl get endpoints -n toast
NAME ENDPOINTS AGE
backend-svc 10.244.1.4:8080,10.244.2.4:8080,10.244.3.3:8080 6m9s
kubectl describe endpoints -n toast # ==> 여기서도 각 POD 의 아이피 확인 가능
Name: backend-svc
Namespace: dev
Labels: <none>
Annotations: endpoints.kubernetes.io/last-change-trigger-time: 2025-12-25T08:52:17Z
Subsets:
Addresses: 10.244.1.4,10.244.2.4,10.244.3.3
NotReadyAddresses: <none>
Ports:
Name Port Protocol
---- ---- --------
<unset> 80 TCP
Events: <none>
## Pod 를 삭제하고, endpoints 정보를 다시 조회하면,
## rs 에 의해서 다시 생성된 Pod 의 신규 IP 가 endpoints 에 다시 적용된다.
# 신규 서비스 생성, deploy 기반으로 생성
# kubectl expose deploy some-deploy --type=ClusterIP \
# --port:8081 --target-port=80 \
# --name=some-svc -n toast
k8s 클러스터에서 dns 를 관리하는 파드가 따로 있고 그게 coredns 인데,
확인을 하려면 아래처럼 조회할 수 있다.
ToastBread → kubectl get po -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-7db6d8ff4d-64zpq 1/1 Running 23 (11m ago) 15d
coredns-7db6d8ff4d-vchls 1/1 Running 23 (11m ago) 15d
# ... 생략 ...
coredns 가 k8s 에서 DNS 서비스를 제공하는 파드이다.Core DNS 도 Deployment 형태로 실행되고 있기 때문에kubectl get deploy 로 확인 가능하다.ToastBread → kubectl get deploy -n kube-system
NAME READY UP-TO-DATE AVAILABLE AGE
coredns 2/2 2 2 15d
# ... 생략 ...
ToastBread → kubectl get svc -n kube-system -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 15d k8s-app=kube-dns
# ... 생략 ...
여기서 보이는
10.96.0.10이 바로 모든 pod 들이 갖는nameserver의IP다.
이를 한번 확인해보자.
# Pod 내부로 접근
kubectl exec -it toast-deploy-5ccd56fb5d-k69kc -- sh
#
/ # cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10 ## ==> 아까 본 nameserver ip 와 동일하다!
options ndots:5
/ #
이런 coredns 와 관련해서 POD 의 nameserver 등록이 자동으로 된 덕분에
pod 안에서 nslookup {service명} 을 입력하면 서비스의 아이피값이 가져올 수 있고,
다른 namespace 에 있는 경우에는 nslookup {service명}.{namespace명} 을 붙여서 요청을 날리수도 있다.
curl 서비스_이름
curl 서비스_이름.네임스페이스_이름
ConfigMap 은 컨테이너 실행 시에 필요한 다양한 환경변수를 관리하는 리소스다.
그런데 환경변수란 게 뭘까?
이런 환경변수를 그런데 왜 굳이 리소스로 거창하게 따로 관리할까?
이건 클라우드 네이티브 환경을 구성하는 주요 요건 중 하나인 설정 외부화 때문이다.
애플리케이션은 개발, 스테이징, 운영 환경으로 주로 구성된다.
각 환경별로 다르게 적용되는 값을 설정(Configuration)이라고 부른다.
설정값들을 소스코드에 저장하면 여러 문제가 발생한다.
설정 외부화(Configuration Externalization)은 클라우드 네이티브 애플리케이션의 필수 설계 원칙 중 하나이다.
환경 변수나 설정 파일을 통해 외부에서 설정을 주입하는 게 핵심이다.
apiVersion: v1
kind: Pod
metadata:
name: busybox
spec:
containers:
- name: busybox
image: busybox
env: # --> 파드의 POD_ENV 환경변수에 env_test 값 주입
- name: POD_ENV
value: "env_test"
위처럼 각각의 POD 별로 이렇게 환경변수를 세팅하면,
여러 POD 에 해야될 때는 상당히 불편해진다.
그래서 파드의 환경변수를 env 의 key-value 형식으로 관리하기 보다는
ConfigMap 과 Secret 을 참조하는 형태로 설정을 하는 경우가 대다수다.
apiVersion: v1
kind: ConfigMap
metadata:
name: my-configmap
data:
db_host: "my-app"
log_level: "DEBUG"
max_connetion: "100"
apiVersion: v1
kind: Secret
metadata:
name: my-secret
data:
db_username: dXNlcgo=
db_password: cGFzc3dvcmQ=
# --> 저장할 데이터 (Base64 인코딩 되어 있음)
# 일일이 저장할 때마다 변환해서 넣는 건 힘들기 때문에 일반적으로는 아래처럼 함.
apiVersion: v1
kind: Secret
metadata:
name: my-secret
stringData:
db_username: "user"
db_password: "password"
# 이러면 알아서 Base64 로 인코딩해서 넣는다.
apiVersion: v1
kind: Pod
metadata:
name: toast-pod
spec:
containers:
- name: nginx-toast
image: nginx:alpine-slim
envFrom:
- configMapRef:
name: (컨피그맵명)
- secretRef:
name: (시크릿명)
# 이러면 env 필드에 key-value 로 넣던 방식과 동일한 효과를 갖는다.
kubectl create configmap 컨피그맵명 --from-literal=키1=값1 --from-literal=키2=갑2
kubectl create secret generic 시크릿명 --from-literal=키1=값1 --from-literal=키2=갑2
# generic 은 시크릿의 다양한 종류 중 하나임!
ToastBread → kubectl create cm toast-config -n toast \
--from-literal=REDIS_HOST=redis \
--from-literal=REDIS_PORT=6379
# configmap/hitchecker-config created
ToastBread → kubectl get cm -n toast
NAME DATA AGE
toast-config 3 13s
kube-root-ca.crt 1 5m29s
ToastBread → kubectl describe cm toast-config -n toast
# 아래는 출력:
Name: toast-config
Namespace: toast
Labels: <none>
Annotations: <none>
Data
====
REDIS_HOST:
----
redis
REDIS_PORT:
----
6379
BinaryData
====
Events: <none>
# ---
ToastBread → kubectl get cm hitchecker-config -o yaml -n dev
apiVersion: v1
data:
REDIS_HOST: redis
REDIS_PORT: "6379"
kind: ConfigMap
metadata:
creationTimestamp: "2025-12-31T11:33:29Z"
name: toast-config
namespace: toast
resourceVersion: "590730"
uid: fcd8711d-0f0c-4d7d-86f2-382489f5fa9f
ConfigMap 과 Secret 은 사용자 관점에서 단순히 Base64 인코딩을 했냐
안했냐의 차이만 있을 뿐 큰 차이를 못 느낀다.
하지만 쿠버네티스 시스템의 리소스 관리, 보안, 권한 분리 등을 위해서 분리되어 설계되었다.
사용자와 쿠버네티스에서 실행중인 애플리케이션에 접속을 도와주는 게 Ingress 이다.
쿠버네티스의 Service 는 L4 기반(IP 와 Port 기준) 의 트래픽 라우팅하고,
반면에 Ingress 는 L7(요청의 URL 기준) 을 사용하여 트래픽을 라우팅을 한다.
Service 는 내부 Pod 들 끼리 통신할 때만 쓰고,
외부 요청은 Ingress 에게 맡기는 게 일반적이다.
Ingress 는 Service 로 네트워크 트랙픽을 전달하는 것을 목적으로 만들어졌다.
요청의 도메인과 경로에 따라 어떤 서비스로 연결할지를 지정한다.
Ingress 는 외부에서 app 으로 들어오는 요청의 entrypoint 역할도 수행.
여러개의 노드로 실행되는 클러스터이다 보니,
쿠버네티스는 여러개의 Nodre 에 공통으로 접근할 수 있는 로드밸런서가 필요하다.
Node 에 L4 로드 밸런서 로 연결하며,
외부 사용자는 로드 밸런서를 통해 쿠버네티스 클러스에 접속한다.
사용자 접속 -> Node 의 로드밸런서 서버 -> Nginx Ingress Controller -> Service -> Pod 순이다.
결과적으로 사요자는 Node 의 로드 밸런서 서버 IP 만 알기 되기 때문에 사용자 입장에서 편하다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: toast-ingress
namespace: toast
spec:
ingressClassName: nginx
rules:
- host: toast-manager.com # 사용자가 접속할 도메인, 현재 도메인이 없다면 생략 가능.
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: toast-frontend
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: toast-user
port:
number: 80
kubectl get ingress -n toast
NAME CLASS HOSTS ADDRESS PORTS AGE
toast-ingress nginx * 80 12s
ToastBread → kubectl describe ingress -n toast
Name: toast-ingress
Labels: <none>
Namespace: toast
Address:
Ingress Class: nginx
Default backend: <default>
Rules:
Host Path Backends
---- ---- --------
*
/ toast-frontend:80 (10.244.1.2:80)
/users toast-user:80 (10.244.1.3:5000)
Annotations: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 31s nginx-ingress-controller Scheduled for sync
쿠버네티스의 컨트롤 루프는
관찰 -> 비교 -> 조치를 통해서 원하는 상태를 실제 상태로 바꾼다.
그런데 이 컨트롤 루프는 정확히 어디서 실행되는 걸까?
쿠버네티스는 클러스터 전체를 제어/관리하는 소프트웨어이고,
이러한 역할은 컨트롤 플레인이라는 핵심 구성요소들이 담당한다.
"컨트롤 플레인의 구성요소들은 Pod 형태로 실행"되며,
우리가 생성하는 일반 Pod 가 아닌, 쿠버네티스 설치시 실행되는 시스템 Pod 이다.
이때 컨트롤 플레인의 Pod 들도 결국은 Pod 이기 때문에 Node 에 실행되어야 한다.
다만 이 Pod 들은 특별하게 Control Plane Node 에서 실행된다.
일반적으로 사용자들이 생성한 Pod 들은 Worker Node 에 생성된다.
즉 Node 의 종류가 2가지로 나뉜다는 점을 알아야 한다.
컨트롤 플레인 파드가 실행되는 노드를 Control Plane Node (컨트롤 플레인 노드)라고 한다.
사용자가 생성한 파드가 실행되는 노드를 Worker Node(워커 노드)라고 한다.
ToastBread → kubectl get node
NAME STATUS ROLES AGE VERSION
control-plane Ready control-plane 26d v1.30.0
worker Ready <none> 26d v1.30.0
worker2 Ready <none> 26d v1.30.0
worker3 Ready <none> 26d v1.30.0
# kubectl describe node 노드명 # 상세정보
# kubectl top node # 자원 사용량 조회
kubelet 은 각 Node(Control Plane/Worker) 에 설치되는 소프트웨어.
kubelet 은 컨트롤 플레인의 지시에 따라 컨테이너 런타임에 컨테이너 관리 작업을 요청한다.
컨트롤 플레인이 요청하는 대로 컨테이너를 실행하거나 상태정보를 조회할 수 있다.
클러스터의 중심 두뇌로, 전체 상태를 관리하고 제어한다.
컨트롤 플레인 은 다음과 같이 구성
etcd 라는 클러스터 상태를 저장하는 DB 가 존재한다.
"컨트롤 플레인 파드"와 "etcd 파드"는 모두 Control Plane Node 에서 실행된다.
컨트롤 플레인 구성요소들은 kube-system namesapce 에서 pod 로 실행된다.
kubectl get po -n kube-system -o wide
ToastBread → kubectl get po -n kube-system -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
etcd-control-plane 1/1 Running 1 (121m ago) 13h 172.22.0.3 control-plane <none> <none>
kube-apiserver-control-plane 1/1 Running 1 (121m ago) 13h 172.22.0.3 control-plane <none> <none>
kube-controller-manager-control-plane 1/1 Running 28 (121m ago) 21d 172.22.0.3 control-plane <none> <none>
kube-scheduler-control-plane 1/1 Running 28 (121m ago) 21d 172.22.0.3 control-plane <none> <none>
# 더 많은 데 생략함.
Work Node 는 사용자가 생성한 Pod 가 실행되는 Node
스케줄러는 새로 생성된 Pod 를 실행할 가장 적절한 Node 를 선택
사용자가 Pod 를 생성 요청하면 스케줄러가 전체 Node 중 조건에 맞는 1개를 선택
Node 스케줄링을 제어하는 4개의 방법
스케줄러를 거치지 않고, 해당 Node 에 즉시 배치된다.
테스트용도로 주로 사용한다고 한다.
apiVersion: v1
kind: Pod
metadata:
name: fixed-node-pod
spec:
nodeName: node4 # 이렇게 직접 지정가능
containers:
- name:app
image: busybox:1.35
apiVersion: v1
kind: Pod
metadata:
name: fixed-node-pod
spec:
nodeSelector: # !
disktype: ssd # !
containers:
- name:app
image: busybox:1.35
표현식 기반으로 Node 선택
nodeSelecotr 보다 유연하다, 다만 복잡하다!
Control Plane Node 는 컨트롤 플레인 파드를 실행하므로,
사용자가 생성한 Pod 와 물리적으로 분리되어야 한다.
특정 Node 에 Pod 를 올리지 못하도록 제한하는 것을 Taint(테인트)라고 한다.
기본적으로 Control Plane Node 는 아래와 같은 Taint 가 설정되어 있다.
# Taint: 스케줄링 금지 조건
key: node-role.kubernetes.io/control-plane
effect: NoSchedule
그러면 어떤 파드를 설정할 수 있을까?
그건 Pod 에 Toleration(참을 수 있는) 설정이 있는 것들이다.
apiVersion: v1
kind: Node
metadata:
name: cpu-node
spec:
taints:
- key: resource
value: highcpu
effect: NoSchedule
apiVersion: v1
kind: Pod
metadata:
name: tolerate-highcpu
spec:
tolerations:
- key: resource
value: highcpu
effect: NoSchedule
operator: Equal
containers:
- name: busybox
image: busybox
참고:
kubectl taint node 노드명 <key>=<value>:<effect> # Node 에 Taint 설정 kubectl taint node 노드명 <key>:<effect> # Node 에 설정된 Taint 제거
참고로 control plane node 의 경우 아래와 같은 Taint 가 있다.
ToastBread → kubectl describe node control-plane
Name: control-plane
Roles: control-plane
# ... 생략 ...
Taints: node-role.kubernetes.io/control-plane:NoSchedule
Job 은 이런 일회성 작업을 실행하기 위한 워크로드 리소스이다.
작업 실행을 위해 Pod 를 일시적으로 생성하며, 작업 완료 시 자동으로 종료된다.
잡이 실행하는 Pod 에는 CMD 에 배치 프로그램을 실행하도록 지정해야 한다.
apiVersion: batch/v1
kind: Job
metadata:
name: toast-job
spec:
template:
metadata:
labels:
app: my-batch
spec:
containers:
- name: my-batch
image: my-batch:1.0
command: ["java","-jar", "batch.jar"] # CMD 지정 !!!
restartPolicy: Never
ToastBread → kubectl get job # 아래와 같은 3가지 상태가 존재한다.
NAME STATUS COMPLETIONS DURATION AGE
job-fail Failed 0/1 19s 19s
job-running Running 0/1 19s 19s
job-success Complete 1/1 5s 19s
# Job 의 상태는 결국 Pod 의 상태에 달렸다.
Job 은 생성한 Pod 가 실패하면, Job 이 성공할 때까지 새 Pod 를 계속 생성하여 실행한다.
설정한 횟수 이상 실패하면, Job 은 더 이상 Pod 를 생성하지 않으며, Job 은 Failed 상태로 전환된다.
Pod 가 정상적으로 종료되면, Job 은 Succeeded 상태로 전환된다.
Job 은 실행 방식과 동작 조건을 다양한 옵션으로 설정할 수 있다.
Pod 의 실행 횟수, 병렬 처리, 실패 시 재시도 횟수, 제한 시간등을 제어할 수 있다.
옵션키:
apiVersion: batch/v1
kind: Job
metadata:
name: my-job
spec:
completions: 3 # 파드가 3번 성공해야 잡 성공
parallelism: 2 # 동시에 최대 2개의 파드가 실행됨
backoffLimit: 4 # 파드가 실패하면 4번까지는 다시 실행, 5번째에서는 잡 실패로 간주
activeDeadlineSeconds: 120 # 잡이 120초 안에 끝나지 않으면 실패
ttlSecondsAfterFinished: 60 # 잡이 끝나면 60초 후 자동 삭제/ 파드는 삭제되지 않고 남아 있음
ex:
apiVersion: batch/v1
kind: Job
metadata:
name: job-completions
spec:
completions: 3
parallelism: 1
template:
spec:
containers:
- name: c
image: busybox:1.35
command: ["sh", "-c", "echo '1회 실행'; sleep 5"]
restartPolicy: Never
---
apiVersion: batch/v1
kind: Job
metadata:
name: job-timeout
spec:
activeDeadlineSeconds: 10 # 10 초가 지나면 실패로 간주!
ttlSecondsAfterFinished: 40 # Job 끝나고 40 초 지난 후 Job 삭제
template:
spec:
containers:
- name: timeout
image: busybox:1.35
command: ["sh", "-c", "echo '30초 후 완료'; sleep 30"]
restartPolicy: Never
apiVersion: batch/v1
kind: CronJob
metadata:
name: one-minute-cronjob
spec:
schedule: "* * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: echo-container
image: busybox:1.35
args:
- /bin/sh
- -c
- |
sleep 10
echo "executed successfully at $(date)"
restartPolicy: Never
총 5개의 필드 - 분,시,일,월,요일을 나타낸다.
아래는 예시:
* * * * * : 모든 경우의 수, 모든 요일, 매월, 매시, 매분5 20 * * * : 매일 밤 8시 5분 실행0 23 * * 0,6 : 토요일, 일요일 23:00분 실행*/5 * * * * : 매 5분마다 실행0 9-17/2 * * * : 9~17시 사이에 2시간 간격으로 실행 (9, 11, 13, 15, 17 시)0 0 1-15 * 1-5 : 매월 1~15일 중 월~금요일 자정에 실행
ToastBread → kubectl get cronjob,job,pod
NAME SCHEDULE TIMEZONE SUSPEND ACTIVE LAST SCHEDULE AGE
cronjob.batch/one-minute-cronjob * * * * * <none> False 0 34s 2m43s
NAME STATUS COMPLETIONS DURATION AGE
job.batch/one-minute-cronjob-29462578 Complete 1/1 13s 2m34s
job.batch/one-minute-cronjob-29462579 Complete 1/1 13s 88s
job.batch/one-minute-cronjob-29462580 Complete 1/1 14s 28s
NAME READY STATUS RESTARTS AGE
pod/one-minute-cronjob-29462578-rdf7n 0/1 Completed 0 2m34s
pod/one-minute-cronjob-29462579-gjbrv 0/1 Completed 0 88s
pod/one-minute-cronjob-29462580-wfgcj 0/1 Completed 0 28s
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: my-daemonset
spec:
selector:
matchLables:
app: sample-daemon
template: # DaemonSet 이 관리하는 Pod 정의
metadata:
labels:
app: sample-daemon
spec:
containers:
- name: busybox-daemon
image: busybox:1.35
replicaSet 과 달리 숫자를 지정하지 않는다.
어짜피 모든 Node 에 실행될 거니까!