78일차) 2025-04-17(컨테이너의 헬스체크-readinessProbe, 리소스-StatefulSet, DaemonSet)
컨테이너의 헬스체크
livenessProbe
livenessProbe가 실패시 컨테이너를 재시작, 컨테이너에 이상이 있다고 간주
readinessProbe
실패해도 정상동작하지만, 서비스에서 해당 파드로 트래픽을 인가하지 않음
보통 livenessProbe보다 readinessProbe를 많이 사용
- 기존 상위 리소스까지 전부 삭제
kubectl delete deploy --all
kubectl delete rs --all
kubectl delete pod --all
httpGet을 통한 probe
root@master:~/mani# mkdir probe
root@master:~/mani# cd probe
vi pod.yml
apiVersion: v1 kind: Pod metadata: name: nginx-pod labels: app: nginx spec: containers: - name: nginx image: 61.254.18.30:5000/nginx #일반적인 pod의 컨테이너 정의 readinessProbe: httpGet: path: / port: 80 periodSeconds: 5 initialDelaySeconds: 2 |
# periodSeconds: 헬스체크 주기 , initialDelaySeconds: 부팅대기시간
# 처음에 2초 대기후 5초에 한번씩 curl localhost/ 와 비슷하다고 생각하면 좋다.
kubectl apply -f pod.yml
kubectl get pod -o wide
kubectl describe pod nginx-pod | grep -i readiness
# -I 반환 코드 조회. 200 성공코드 반환.
보통 내가 만든 앱이 성공코드를 반환하지 않는 앱이라면, /health 같은 경로에 성공코드를 반환하는 간단한 페이지를 만들어두고 해당 경로로 readiness를 통해 헬스체크를 하면 좋다.
실패하는 경우 어떻게 되는지 보자
cp pod.yml rf-pod.yml
vi rf-pod.yml
kubectl apply -f rf-pod.yml
- rf-nginx-pod는 두고 nginx-pod를 삭제
kubectl delete pod nginx-pod
- ClusterIP 타입(기본값)으로 svc 생성
vi svc.yml
apiVersion: v1
kind: Service
metadata:
name: svc-test
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
kubectl apply -f svc.yml
♨ 서비스의 클러스터 IP로 접속이 안된다면
- 파드는 정상 동작하므로 파드의 IP로 curl 보냄
# pod는 정상동작하지만, READY가 0/1. 이로인해 결국 svc에서 pod로 트래픽이 인가되지 않는다.
root@master:~/mani/probe# kubectl exec -it rf-nginx-pod -- bash
root@rf-nginx-pod:/# cat run/nginx.pid
kubectl edit pod rf-nginx-pod
# 현재 동작중인 pod의 status 확인
# 매니페스트의 포트를 80으로 변경하고 다시 apply
kubectl delete -f rf-pod.yml
kubectl apply -f rf-pod.yml
- readinessProbe 성공
실습)
61.254.18.30:5000/nginx 이미지로 httpGet 방식의 readiness를 통해 /test경로에 80번 포트로의 요청이 성공하도록 만들어보세요!
- svc.yml
root@master:~/mani/probe# vi svc.yml
apiVersion: v1
kind: Service
metadata:
name: svc-test
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
root@master:~/mani/probe# kubectl apply -f svc.yml
- configmap.yml
root@master:~/mani/probe# vi configmap.yml
# configmap.yml
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-conf
data:
default.conf: |
server {
listen 80;
location /test {
return 200 'ok';
add_header Content-Type text/plain;
}
location / {
root /usr/share/nginx/html;
index index.html;
}
}
root@master:~/mani/probe# kubectl apply -f configmap.yml
- pod.yml
root@master:~/mani/probe# vi pod.yml
# pod.yml
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx
spec:
containers:
- name: nginx
image: 61.254.18.30:5000/nginx
ports:
- containerPort: 80
readinessProbe:
httpGet:
path: /test
port: 80
initialDelaySeconds: 2
periodSeconds: 5
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/conf.d
volumes:
- name: nginx-config
configMap:
name: nginx-conf
root@master:~/mani/probe# kubectl apply -f pod.yml
- 확인
root@master:~/mani/probe# kubectl get pod
root@master:~/mani/probe# kubectl get pod -o wide
root@master:~/mani/probe# kubectl describe pod nginx-pod | grep -i readiness
root@master:~/mani/probe# curl -I 10.244.2.138
root@master:~/mani/probe# curl 10.244.2.138
root@master:~/mani/probe# kubectl exec -it nginx-pod -- bash
configMap으로 index파일을 만들어서 컨테이너에 /usr/share/nginx/html/test 경로에 넣어줄 예정
vi test.yml
apiVersion: v1 kind: ConfigMap metadata: name: cm-index data: index.html: | readiness success --- apiVersion: v1 kind: Pod metadata: name: test-pod spec: containers: - name: nginx image: 61.254.18.30:5000/nginx volumeMounts: - name: vol-index mountPath: /usr/share/nginx/html/test readinessProbe: httpGet: path: /test port: 80 initialDelaySeconds: 3 periodSeconds: 5 volumes: - name: vol-index configMap: name: cm-index |
실습2)
실패하는 livenessProbe를 구성해보세요. 이후에 kubectl get pod --watch 같은 옵션을 통해 어떤 현상이 발생하는지 한번 확인해보세요.
일부러 실패하게 만들기
vi pod.yml
- 방법 A. 잘못된 포트 사용 → 실패
livenessProbe:
httpGet:
path: /test
port: 800 # nginx는 실제 80포트만 사용 중이므로 실패
initialDelaySeconds: 2
periodSeconds: 5
➡️ 실패 → kubectl get pod --watch 시 재시작 반복됨
- 방법 B. 존재하지 않는 경로 요청 → 실패
livenessProbe:
httpGet:
path: /notfound
port: 80
initialDelaySeconds: 2
periodSeconds: 5
➡️ /notfound는 404 반환 → 실패
kubectl apply -f pod.yml
kubectl get pod --watch
kubectl describe pod nginx-pod | grep -A 10 Liveness
kubectl logs nginx-pod
풀이)
vi lf-pod.yml
apiVersion: v1 kind: Pod metadata: name: lf-nginx-pod labels: app: lf-nginx spec: containers: - name: nginx image: 61.254.18.30:5000/nginx livenessProbe: httpGet: path: /fail #curl localhost:80/fail port: 80 periodSeconds: 5 initialDelaySeconds: 5 |
- 삭제
kubectl delete pod lf-nginx-pod
vi lf-pod.yml
tcpSocket 을 통한 Probe
MySQL 같은 앱은 httpGet같은 요청으로는 응답을 주지 않는다. 따라서, MySQL의 기본 포트인 3306으로 tcpSocket 이 유효한지를 검증하여 probe하는 방식
vi redis.yml
apiVersion: v1 kind: Pod metadata: name: redis labels: app: redis spec: containers: - name: redis image: 61.254.18.30:5000/redis readinessProbe: tcpSocket: port: 6379 initialDelaySeconds: 5 periodSeconds: 3 |
kubectl apply -f redis.yml
command를 통한 Probe
- $? : 직전에 실행한 명령이 어떻게 종료되었는지 종료코드를 조회
- 정상종료=0 반환, 그렇지 않은경우 1이나 2를 반환
0을 반환하면 readinessProbe가 성공, 그렇지 않은 경우 실패
- 이 특성을 갖고 헬스체크를 해보면
vi cmd-mysql.yml
apiVersion: v1
kind: Pod
metadata:
name: mysql
spec:
containers:
- name: mysql
image: 61.254.18.30:5000/mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: "password"
- mysql 컨테이너를 띄워보자
kubectl apply -f cmd-mysql.yml
- 어떤 명령이 수행됐을때 0을 반환하는지 모르기때문에 한번 들어가서 확인
kubectl exec -it mysql -- bash
- Ctrl+D로 컨테이너에서 빠져나온후 파드를 삭제
kubectl delete pod mysql
vi cmd-mysql.yml
apiVersion: v1 kind: Pod metadata: name: mysql spec: containers: - name: mysql image: 61.254.18.30:5000/mysql env: - name: MYSQL_ROOT_PASSWORD value: "password" readinessProbe: exec: command: ["mysqladmin","ping","-ppassword"] initialDelaySeconds: 10 periodSeconds: 3 |
kubectl apply -f cmd-mysql.yml
실습)
readinessProbe: exec: command: ["cat","/test/test.txt"] initialDelaySeconds: 10 periodSeconds: 3 |
readinessProbe가 성공하는 pod 매니페스트 파일을 작성해보세요.
root@master:~/mani/probe# vi testpod.yml
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx
spec:
containers:
- name: nginx
image: 61.254.18.30:5000/nginx
command: ["/bin/sh", "-c", "mkdir -p /test && echo hello > /test/test.txt && nginx -g 'daemon off;'"]
ports:
- containerPort: 80
readinessProbe:
exec:
command: ["cat", "/test/test.txt"]
initialDelaySeconds: 10
periodSeconds: 3
root@master:~/mani/probe# kubectl delete pod nginx-pod
root@master:~/mani/probe# kubectl apply -f testpod.yml
풀이)
- pv-pvc로 호스트에 존재하는 파일을 넣어주기 - 별로
- configMap - 세련
- command - 금지
command 명령 = Dockerfile CMD, CMD가 무시되고, 컨테이너가 제대로 동작 안함 - Dockerfile을 통한 이미지빌드. - 귀찮
cp test.yml cmd-nginx.yml
vi cmd-nginx.yml
apiVersion: v1 kind: ConfigMap metadata: name: cm-test data: test.txt: | cat success --- apiVersion: v1 kind: Pod metadata: name: cmd-pod spec: containers: - name: nginx image: 61.254.18.30:5000/nginx volumeMounts: - name: vol-index mountPath: /test readinessProbe: exec: command: ["cat","/test/test.txt"] initialDelaySeconds: 10 periodSeconds: 3 volumes: - name: vol-index configMap: name: cm-test |
kubectl apply -f cmd-nginx.yml
# 내부에 파일이 잘 만들어진걸 확인 가능, readiness가 성공한것 자체가 이미 /test/test.txt가 잘 주입된걸 의미
StatefulSet
파드의 이름을 규칙적으로 생성하고 싶을 때 사용하는 리소스
kubectl create deploy test --replicas=2 --image=61.254.18.30:5000/ipnginx
# 우리가 deployment를 통해 pod를 여러개 띄우면 pod의 이름이 우리가 예상할 수 없는 형태의 랜덤한 이름이 부여가 된다.
딱히 내가 앱을 배포할때 pod의 이름이 크게 중요하지 않다면, 빠르게 배포하고 싶다면 이렇게 pod를 생성하면 된다. 하지만 때에 따라서는 내가 pod의 이름을 신경써야하는 경우도 있다. 가령, 예를 들자면 mysql을 구성하는데, 하나는 master로 구성하고, 다른 하나는 slave로 구성을 하는경우가 그렇다. 그래서 내가 첫번째 리소스가 항상 mysql-0 으로 생성되고, 두번째 리소스는 항상 mysql-1 이런식으로 구성된다면, mysql-0을 마스터로 구성하고, mysql-1을 slave로 구성하면 될것이다.
이렇게 일정한 이름을 갖도록 구성되는 리소스가 StatefulSet이다.
vi sts.yml
apiVersion: apps/v1 kind: StatefulSet metadata: name: sts-mysql spec: serviceName: "mysql" #반드시 svc를 이 이름으로 만들어줘야한다 replicas: 3 selector: matchLabels: app: sts-mysql template: metadata: labels: app: sts-mysql spec: containers: - name: sts-mysql image: 61.254.18.30:5000/mysql ports: - containerPort: 3306 env: - name: MYSQL_ROOT_PASSWORD value: "password" |
DaemonSet
각 노드마다 무조건 한개씩만 파드를 띄우고 싶을때 사용하는 리소스 (도커스웜에서 deploy.mode.global: 옵션과 비슷)
kube-system의 kube-proxy, metallb-system의 speaker 같은 리소스가 이에 해당
모니터링 시스템처럼 모든 노드에 자원을 수입할 exporter들을 배치하고 싶은 경우
vi ds.yml
apiVersion: apps/v1 kind: DaemonSet metadata: name: ds-nginx spec: selector: matchLabels: app: ds-nginx template: metadata: labels: app: ds-nginx spec: containers: - name: ds-nginx image: 61.254.18.30:5000/nginx |
kubectl apply -f ds.yml
Quiz)
k8s 클러스터에 600gram이라는 이름의 deployment를 생성해서, replicas=7로 nodePort는 30500으로 배포하였다. 이 deployment를 통해 'k8s joa'라는 내용의 인덱스 파일을 배포했다. 몇 분 후 'k8s siro'라는 내용을 갖는 인덱스 파일을 포함하는 mypod라는 이름의 Pod를 nodePort 31000으로 배포했는데, 이상하게 계속 'k8s joa'라는 내용만 뜨는것이었다.
이 현상을 재현해보세요.
kubectl delete deploy --all
kubectl delete rs --all
kubectl delete svc --all
kubectl delete pod --all
- 조아
root@master:~/mani/600g# vi joa-deploy.yml
# joa-deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: 600gram
spec:
replicas: 7
selector:
matchLabels:
app: k8s-joa
template:
metadata:
labels:
app: k8s-joa
spec:
containers:
- name: web
image: nginx
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
configMap:
name: joa-index
---
apiVersion: v1
kind: ConfigMap
metadata:
name: joa-index
data:
index.html: |
k8s joa
---
apiVersion: v1
kind: Service
metadata:
name: svc-joa
spec:
type: NodePort
selector:
app: k8s-joa
ports:
- name: joa-main
port: 80
targetPort: 80
nodePort: 30500
root@master:~/mani/600g# kubectl apply -f joa-deploy.yml
- 시로
root@master:~/mani/600g# vi siro-pod.yml
# siro-pod.yml
apiVersion: v1
kind: Pod
metadata:
name: mypod
labels:
app: k8s-siro
spec:
containers:
- name: web
image: nginx
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
ports:
- containerPort: 80
volumes:
- name: html
configMap:
name: siro-index
---
apiVersion: v1
kind: ConfigMap
metadata:
name: siro-index
data:
index.html: |
k8s siro
---
apiVersion: v1
kind: Service
metadata:
name: svc-siro
spec:
type: NodePort
selector:
app: k8s-joa
ports:
- port: 80
targetPort: 80
nodePort: 31000
root@master:~/mani/600g# kubectl apply -f siro-pod.yml
풀이)
pod에 동일한 라벨링을 하면 어떻게 되는지?
서비스는 라벨을 기준으로 트래픽을 인가하기때문에 같은 라벨을 달고있으면 해당 라벨을 달고 있는 모든 pod에 트래픽을 인가시킬것. 따라서 31000 포트로 접속해도 siro에 갈 확률이 1/8 밖에 안된다.