2025 CKA 시험을 준비하면서 개인적으로 정리했던 내용들입니다.
한번 쭉 훑어보시는 것도 도움될 것 같아서 올려봅니다.
내용은 대부분 이전 포스팅에서 알려드렸던 udemy, youtube 등을 참고한 문제풀이 및 개인적으로 정리한 내용입니다.
이 내용 외에도 알아야 하는 것이 많으니 참고만 해주세요.
2025 CKA 합격 후기 / 유형 변경 대응법 및 네트워크 문제 경험 공유
목차 개요2025 CKA 유형이 변경되었습니다. 아직 관련 정보가 많이 없어서, 이번 글에서 변경된 유형에 대한 팁을 정리해보려고 합니다.또한 시험 도중에 인터넷이 끊겨 시험이 종료되는 상황이
sunrise-min.tistory.com
목차
설치 & 환경 구성
🌟 deb 패키지 설치
Question No: 11
Complete these tasks to prepare the system for Kubernetes:
Set up cri-dockerd:
- Install the Debian package:
~/cri-dockerd_0.3.9.3-0.ubuntu-focal_amd64.deb
- Start the cri-dockerd service.
- Enable and start the systemd service for cri-dockerd.
Configure these system parameters:
- Set net.bridge.bridge-nf-call-iptables to 1.
- Set net.ipv6.conf.all.forwarding to 1.
- Set net.ipv4.ip_forward to 1.
$ dpkg -i ~/cri-dockerd_0.3.9.3-0.ubuntu-focal_amd64.deb
Selecting previously unselected package cri-dockerd.
(Reading database ... 140092 files and directories currently installed.)
Preparing to unpack .../cri-dockerd_0.3.9.3-0.ubuntu-focal_amd64.deb ...
Unpacking cri-dockerd (0.3.9~3-0~ubuntu-focal) ...
Setting up cri-dockerd (0.3.9~3-0~ubuntu-focal) ...
Created symlink /etc/systemd/system/multi-user.target.wants/cri-docker.service → /usr/lib/systemd/system/cri-docker.service.
Created symlink /etc/systemd/system/sockets.target.wants/cri-docker.socket → /usr/lib/systemd/system/cri-docker.socket.
# 실행
$ sudo systemctl enable --now cri-docker
$ sudo systemctl status cri-docker
# 시스템 관련 매개변수 구성
<https://kubernetes.io/docs/setup/production-environment/container-runtimes/>
# sysctl params required by setup, params persist across reboots
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.ipv4.ip_forward = 1
EOF
# Apply sysctl params without reboot
sudo sysctl --system
매개변수 구성 없는 경우,
bob@node01 ~ ✖ sudo dpkg -i /root/cri-docker_0.3.16.3-0.debian.deb
Selecting previously unselected package cri-dockerd.
(Reading database ... 18376 files and directories currently installed.)
Preparing to unpack .../cri-docker_0.3.16.3-0.debian.deb ...
Unpacking cri-dockerd (0.3.16~3-0~ubuntu-jammy) ...
Setting up cri-dockerd (0.3.16~3-0~ubuntu-jammy) ...
Created symlink /etc/systemd/system/multi-user.target.wants/cri-docker.service → /lib/systemd/system/cri-docker.service.
Created symlink /etc/systemd/system/sockets.target.wants/cri-docker.socket → /lib/systemd/system/cri-docker.socket.
/usr/sbin/policy-rc.d returned 101, not running 'start cri-docker.service cri-docker.socket'
bob@node01 ~ ✖ sudo systemctl enable --now cri-docker
🌟Calico install
The CNI you choose must satisfy following requirement : <native network policy support>
Install and configure a Container Network Interface (CNI) that satisfies the following conditions.
You may choose one of the following options:
- Flannel using the manifest
<https://github.com/flannel-io/flannel/releases/download/v0.26.1/kube-flannel.yml>
- Calico using the manifest
<https://raw.githubusercontent.com/projectcalico/calico/v3.29.2/manifests/tigera-operator.yaml>
The chosen CNI must allow:
- Pod-to-Pod communication
- NetworkPolicy enforcement
- Must be installed using manifest files (do not use Helm)
다음 조건을 만족하는 CNI(Container Network Interface)를 설치하고 구성하세요.
아래 옵션 중 **하나를 선택하여** 설치할 수 있습니다:
- Flannel (매니페스트 사용)
<https://github.com/flannel-io/flannel/releases/download/v0.26.1/kube-flannel.yml>
- Calico (매니페스트 사용)
<https://raw.githubusercontent.com/projectcalico/calico/v3.29.2/manifests/tigera-operator.yaml>
선택한 CNI는 반드시 다음 조건을 만족해야 합니다:
- Pod 간 통신이 가능해야 함
- NetworkPolicy를 지원해야 함
- Helm을 사용하지 말고 매니페스트 파일을 사용하여 설치해야 함
이 문제에서는, CNI 를 두개 다 설치하라는 것이 아니라 다음 조건을 만족하는 CNI를 선택해서 설치하라는 것
- 문제에 “NetworkPolicy를 지원해야 한다” 라고 되어 있음 → 무조건 Calico
- 단순히 Pod 간 통신만 되게 해라 → Flannel이 빠르고 편함
calico 설치 방법
# create로 설치해야 한다! apply 사용 X
🌟 k create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.2/manifests/tigera-operator.yaml
# 테스트
kubectl run test1 --image=busybox --restart=Never -- sleep 3600
kubectl run test2 --image=busybox --restart=Never -- sleep 3600
kubectl exec test1 -- ping -c 4 test2
# calico pod 생성 확인
k get pods -n kube-system
+++
# calico 설치 시, 옵션 변경이 필요한 부분이 있으면
# curl -o https://.. 로 파일 다운로드 받은 다음 수정합니다.
Flannel Install
문제
Install and configure a Container Network Interface (CNI) of your choice that meets the specified requirements. Choose one of the following CNI options:
Flannel (v0.26.1)
using the manifest:
[kube-flannel.yml]
(<https://github.com/flannel-io/flannel/releases/download/v0.26.1/kube-flannel.yml>)
Calico (v3.28.2)
using the manifest:
[tigera-operator.yaml]
(<https://raw.githubusercontent.com/projectcalico/calico/v3.28.2/manifests/tigera-operator.yaml>)
Ensure the selected CNI is properly installed and configured in the Kubernetes cluster.
여기서는 networkPolicy 같은 조건이 없으므로 Flannel 설치해도 무방함
k apply -f <https://github.com/flannel-io/flannel/releases/download/v0.26.1/kube-flannel.yml>
# 확인
k get po -n kube-flannel
🌟Helm
CRD
CRD = Custom Resource Definition (사용자 정의 리소스 정의)
🌟 사이즈가 크기 때문에 CRD 가 있는 파일은 kubectl create -f 로 설치합니다.
1. CRD 먼저 등록 (Application이라는 리소스 정의)
2. 이제부터 아래 같은 리소스를 쓸 수 있음:
apiVersion: argoproj.io/v1alpha1
kind: Application
kubectl get crd 로 조회 가능
Helm template
- Helm chart를 실제로 Kubernetes에 설치하지 않고, 생성될 리소스들을 YAML 형태로 출력해주는 명령어
helm template argocd argo/argo-cd --version 7.7.3 -n argocd \\
--set crds.install=false > ~/argo-helm.yaml
values 보는 방법
helm show values argo/argo-cd --version 7.7.3
helm template은 렌더링된 YAML 출력, helm pull은 Chart 구조 전체를 디렉토리로 저장
helm install
helm repo add agro https://..
argo-cd 설치 시 아래와 같은 에러가 뜨면, crd 가 이미 설치되어 있다는 것
이때는 —skip-crds 옵션 사용
Error: rendered manifests contain a resource that already exists.
Unable to continue with install: CustomResourceDefinition "applications.argoproj.io" already exists
만약, —skip-crds 옵션이 안먹으면?
아래에서 옵션을 보고, —set crds.install=false 설정.
시험에서 주는 argo 공식 문서에 들어가면 바로 --set crds.install=false 쓰라고 나옴
helm show values argo/argo-cd --version 7.7.3
# 이미 생성된 helm release에 대해 values 조회 가능
helm get values kocoon-hermes -n logging
helm pull
helm pull <chart-name> [flags]
# 압축 해제하고 저장
helm pull argo/argo-cd --version 7.7.3 --untar --untardir ./charts
kustomize
my-kustomize/
├── base/
│ ├── deployment.yaml
│ ├── service.yaml
│ └── kustomization.yaml
└── overlays/
└── prod/
├── kustomization.yaml
└── deployment-patch.yaml
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namespace: prod
namePrefix: prod-
commonLabels: # 모든 리소스에 라벨 추가
env: prod
patchesStrategicMerge:
- deployment-patch.yaml
---
# 리소스 생성
kubectl apply -k overlays/prod
# 적용하지 않고 눈으로만 결과 확인
kubectl kustomize overlays/prod
# yaml 파일로 저장 (제출용)
kubectl kustomize overlays/prod > /root/final.yaml
권한 / 보안
Priorityclass
리소스가 부족할 때 “누굴 먼저 죽이고 누굴 살릴지” 결정하는 기준으로 매우 중요한 역할
즉, Priority class는 파드의 생존 우선순위를 정하는 값이다.
문제
Perform the following tasks:
Create a new PriorityClass named high-priority for user workloads
with a value that is one less than the highest existing user-defined
priority class value.
Patch the existing Deployment busybox-logger running
in the priority namespace to use the high-priority priority class.
Ensure that the busybox-logger Deployment rolls out successfully
with the new priority class set.
Note - It is expected that pods from other Deployments running
in the priority namespace are evicted.
k create priorityclass high-priority --value=999
deployment에서 아래처럼 설정
spec:
priorityClassName: "high-priority"
만약, 파드가 안뜨면?
# k describe pod로 이유 확인
# 강제 재시작
k rollout restart deployment busybox-logger -n priority
spec:
priorityClassName: high-priority
preemptionPolicy: PreemptLowerPriority // 기본값
preemptionPolicy는 기본값이 나보다 우선순위 낮은 pod는 쫓아내고(eviction) 자리를 만든다.
0/3 nodes are available: 1 Insufficient memory, 2 Pods have higher priority
// 3개의 노드 중 어느 곳에서도 스케줄할 수 없음
// 1 Insufficient memory : 한 개의 노드는 메모리가 부족함
// 2 Pods have higher priority and do not preempt : 나머지 2개 노드는 우선순위 높은 Pod 가 있고, 쫓아낼 수 없다
- PreemptionPolicy
- PreemptLowerPriority(기본값) 자신보다 우선순위가 낮은 pod를 쫓아낼 수 있음
- Never : 절대로 다른 pod를 쫓아내지 않음
Pod Security Admission(PSA)
PodSecurityPolicy (PSP) 는 Kubernetes 1.25에서 완전히 제거
PSA(Pod Security Admission) 은 Pod 자체에 설정하는 게 아니라, Namespace에 레이블을 붙여서 해당 네임스페이스 내 모든 Pod을 제어하는 구조
pod-security.kubernetes.io/enforce | 적용할 모드 (restricted / baseline / privileged) |
pod-security.kubernetes.io/enforce-version | 보안 정책의 버전 (예: latest) |
pod-security.kubernetes.io/warn | 적용은 안 하고 경고만 표시 |
pod-security.kubernetes.io/audit | 로그에만 기록 |
- privileged: 제한 거의 없음 (테스트용이나 관리자용)
- baseline: 최소한의 제약
- restricted: 가장 보안 수준이 높은 모드
# 1. 네임스페이스 만들고
kubectl create ns secure-ns
# 2. restricted 정책 적용
kubectl label ns secure-ns \\
pod-security.kubernetes.io/enforce=restricted \\
pod-security.kubernetes.io/enforce-version=latest
# namespace 라벨 조회
k get ns --show-labels
# 예시) restricted는 이런 설정이 있어야 통과
securityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false
securityContext:
runAsUser: 0 # restricted에선 root 금지!
runAsNonRoot: true # root 대신 일반 유저 -> 통과
이제 secure-ns 네임스페이스에서 Pod을 생성하면, Pod가 restricted 정책을 위반할 경우 → 생성 거부
k8s에서 Pod나 리소스를 생성하려고 할 때, API 서버가 생성 전에 “검열” 하는 단계가 있는데 이때 개입하는 것이 바로 Admission Controller이다. PSA는 AdmissionController 중 하나로, Pod 생성 요청이 들어오면 보안 정책을 기준으로 허용/거부 여부를 결정함.
kubectl get ns secure-ns --show-labels
- [PSA(Pod Security Admission)
k label namespace restricted-ns pod-security.kubernetes.io/enforce=restricted
k label namespace restricted-ns pod-security.kubernetes.io/enforce-version=v1.24
securityContext
컨테이너 또는 Pod 수준에서 보안 관련 설정을 지정하는 필드
- privileged : 컨테이너에 호스트 수준의 강력한 권한을 부여할지 여부를 설정하는 옵션
- capabilities:
- securityContext: capabilities: add: ["NET_ADMIN", "SYS_TIME"] drop: ["ALL"]
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
securityContext: # Pod 수준
runAsNonRoot: true
fsGroup: 2000 # 볼륨에 접근할 때 group 권한 부여. pod 수준에서만 가능
containers:
- name: nginx
image: nginx
securityContext: # 컨테이너 수준 <- 보통 이걸 많이쓴다고 함
privileged: false # 웬만하면 false로 하자. 이게 true 라서 안뜨는 문제 많이나옴
allowPrivilegeEscalation: false # 실행 중에 권한 상승을 허용할지 여부
runAsNonRoot: true | root 계정 실행 차단 | ✅ 필수 | 없으면 거의 무조건 막힘 |
runAsUser: 1000 | 특정 UID로 실행 | ✅ 가능 | root UID(0)이면 막힘 |
privileged: true | 호스트급 권한 부여 | ❌ 금지 | 거의 시험에서 거부 이유로 나옴 |
allowPrivilegeEscalation: false | sudo 등 권한 상승 금지 | ✅ 필수 | 없으면 PSA에서 막힘 |
capabilities.add | 위험 기능 추가 | ❌ 금지 | 특히 NET_RAW, SYS_ADMIN |
가능: SYS_TIME | |||
capabilities.drop: ["ALL"] | 기능 모두 제거 | ✅ 권장 | PSA 통과 안정용 |
readOnlyRootFilesystem: true | 루트 파일시스템 읽기 전용 | ✅ 권장 | 필수는 아님 |
Default Mode
Config, Secret 등을 Pod 안에 “파일처럼” 마운트할 때, 그 파일의 퍼미션을 설정하는 옵션
defaultMode = 마운트된 파일 권한 설정으로 기본값은 보통 0644
볼륨 타입이 configMap, secret, downwardAPI처럼 "파일로 마운트되는 리소스"일 때만 가능
디렉토리에는 적용 못하고, 파일에만 적용됨 디렉토리는 무조건 0755로 고정
일반 볼륨(emptyDir, hostPath, PVC)에는 적용 안됨!
volumes:
- name: config-vol
configMap:
name: my-config
defaultMode: 0400 # ← 이게 파일 퍼미션 설정
Taint 종류 보기 (NoSchedule..)
노드에 조건을 걸어서, 특정 Pod만 올라올 수 있게 막는 기능
NoSchedule | Pod이 안 떠서 원인 찾는 문제에 자주 등장 |
NoExecute | 이 테인트를 허용하지 않는 파드는 즉시 evection toleration 설정에 tolerationSeconds 명시하지 않고 이 테인트를 허용하는 파드는 영구 유지 |
PreferNoSchedule | 보통 설명형 문제나 비교 문제로 등장 |
value 없음 | key:NoSchedule (예: node-role.kubernetes.io/master:NoSchedule) |
value 있음 | key=value:NoSchedule (예: env=prod:NoSchedule) |
✅ NoSchedule
“Pod이 특정 노드에 스케줄되지 않음. → kubectl describe node → taint 확인 → 해결책은 toleration 추가
$ kubectl get pod nginx -o wide
NAME READY STATUS RESTARTS AGE NODE
nginx 0/1 Pending 0 1m <none>
$ kubectl describe node node1 | grep Taint
Taints: node-role.kubernetes.io/master:NoSchedule
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
tolerations:
- key: "node-role.kubernetes.io/master"
operator: "Exists"
effect: "NoSchedule"
✅ NoExecute
“Pod이 정상적으로 실행되었으나 일정 시간 후 사라짐”
NoExecute는 조건부로 얼마만큼은 버텨도 된다. 라는 개념이 있다.
→ NoExecute taint로 인해 evicted
→ toleration에 tolerationSeconds 필요
Taints: zone=test:NoExecute
spec:
containers:
- name: nginx
image: nginx
tolerations:
- key: "zone"
operator: "Equal"
value: "test"
effect: "NoExecute"
tolerationSeconds: 60 # 60초만 떠있다가 내려감.
Affinity
- requiredDuringSchedulingIgnoredDuringExecution : 무조건 만족해야 하는데, 스케줄링할 때 검사하고 한번 스케줄링된 다음에는 더이상 신경쓰지 않겠다. (파드가 노드에서 제거evicted 되지 않음)
- preferredDuringSchedulingIgnoredDuringExecution : 필수조건 통과한 노드 중 가중치
- Affinity
- nodeAffinity : 특정 노드에 파드 배치 유도
- podAffinity : 어떤 라벨을 가진 파드와 같은 노드에 배치 유도
- podAntiAffinity : 같은 라벨을 가진 파드가 같은 노드에 배치되지 못하게 함
- nodeAffinity
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values:
- ssd
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
스케줄러가 선호할 노드를 지정할 수 있다.
A에 스케줄링되기를 원하지만, 만약 A에 파드를 위한 공간이 충분치 않거나 스케줄링할 수 없는 다른 중요한 이유가 있는 경우 다른 곳에 스케줄링되어도 된다.
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1 # 1~ 100 까지 있는데 100이 가중치가 높음
preference:
matchExpressions:
- key: disktype
operator: In
values:
- ssd
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
책에 나온 예제
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: availability-zone
operator: In
values:
- zone1
- weight: 20
preference:
matchExpression:
- key: share-type
operator: In
values:
- dedicated
- podAffinity
apiVersion: v1
kind: Pod
metadata:
name: with-pod-affinity
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 필수
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: topology.kubernetes.io/zone
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S2
topologyKey: topology.kubernetes.io/zone
containers:
- name: with-pod-affinity
image: registry.k8s.io/pause:3.8
- topolgyKey
pod affinity 및 anti-affinity에서 topologyKey는 필수이다.
topologyKey 속성은 파드를 배포해서는 안 되는 범위를 결정한다. 이를 사용해 파드가 동일한 랙, 가용 영역, 리전 또는 사용자 지정 노드 레이블을 사용해 만든 사용자 지정 범위에 배포되지 않도록 할 수 있다.
⭐️ topologyKey 값 뒤에만 다른거 아니니까 뒤에만 바꾸지말고 전체 복사 똑바로 하기.
같은 zone 안에 스케줄링 | topology.kubernetes.io/zone |
같은 노드 안에 스케줄링 | kubernetes.io/hostname |
NodeAffinity 랑 PodAffinity 랑 사용법 약간 다름 주의
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S2
topologyKey: topology.kubernetes.io/zone
Pv node affinity
Pv에 node affinity 가 있으면, 해당 pv를 사용하는 Pod도 해당 노드에 떠야 합니다.
pod는 node01에 떠있음, pv는 nodeAffinity가 있어서 node02 전용, pvc-pv 바인딩 상태
pod 에서 pvc 바운드 하려면 실패 (이 노드에서는 해당 볼륨을 사용할 수 없다는 에러메시지)
FailedAttachVolume: volume "static-pv-example" is not attached to the node
pod has unbound immediate PersistentVolumeClaims
pod가 해당 pv 사용하려면, pod를 node02에 뜨게 해야함
spec:
nodeSelector:
kubernetes.io/hostname: node02
nodeAffinity는 스케줄링과 관련된 리소스에 적용 가능
- Pod, PV, StatefulSet, Deployment, Job, DaemonSet
nodeAffinity는 Pod/PV 에서 사용 시 구조가 다릅니다.
Pod | affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution |
PV | nodeAffinity.required.nodeSelectorTerms |
# pod
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node-1
# pv
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values:
- ssd
워크로드
🌟HPA
문제
Create a new HorizontalPodAutoscaler (HPA)
named apache-server in the autoscale namespace.
This HPA must target the existing Deployment called apache-server in the autoscale namespace.
Set the HPA to target for 50% CPU usage per Pod.
• Configure hpa to have at min 1 Pod and no more than 4 Pods [max].
Also, we have to set the downscale stabilization window to 30 seconds.
✅ behavior는 spec 하위에 있음
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: apache-server
namespace: autoscale
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: apache-server
minReplicas: 1
maxReplicas: 4
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
behavior:
scaleDown:
stabilizationWindowSeconds: 30
🌟SideCar
문제
Update the existing deployment synergy-leverager,
adding a co-located container named sidecar using the busybox:stable image
to the existing pod.
The new co-located container has to run the following command:
/bin/sh -c "tail -n+1 -f /var/log/synergy-leverager.log"
Use a volume mounted at /var/log to make the log file synergy-leverager.log
available to the co-located container.
apiVersion: apps/v1
kind: Deployment
metadata:
name: synergy-leverager
labels:
app: synergy-leverager
spec:
replicas: 1
selector:
matchLabels:
app: synergy-leverager
template:
metadata:
labels:
app: synergy-leverager
spec:
containers:
- name: app
image: nginx:latest
volumeMounts:
- name: log-volume
mountPath: /var/log
- name: sidecar
image: busybox:stable
command: ["/bin/sh", "-c", "tail -n+1 -f /var/log/synergy-leverager.log"]
volumeMounts:
- name: log-volume
mountPath: /var/log
volumes:
- name: log-volume
emptyDir: {}
테스트
k get log -f synergy-leverager-abcd -c sidecar
++++++
apiVersion: v1
kind: Pod
metadata:
name: mc-pod
namespace: mc-namespace
spec:
containers:
- name: mc-pod-1
image: nginx:1-alpine
env:
- name: NODE_NAME # 여기 복붙하고 바로지말고 수정 잘해
valueFrom:
fieldRef:
fieldPath: spec.nodeName # 여기중요!
- name: mc-pod-2
image: busybox:1
command:
- sh
- -c
- "while true; do date >> /var/log/shared/date.log; sleep 1; done" # sidecar 예제보면 있음
volumeMounts:
- mountPath: /var/log/shared
name: cache-volume
- name: mc-pod-3
image: busybox:1
command:
- sh
- -c
- "tail -f /var/log/shared/date.log"
volumeMounts:
- mountPath: /var/log/shared
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
1초마다 찍는 이유는, echo는 빠르게 실행되고 끝나기 때문에 컨테이너는 바로 Exit 0 (정상 종료) 상태가 되어버림
그 순간 kubelet이 보기에 이건 “일 끝났네?” → 재시작 시도 → 반복 → CrashLoopBackOff 혹은 Completed 등 상태로 빠짐
log 관련 sidecar 문제는 공식 문서에서 sidecar 검색해서 바로 보는게 편함
command:
- sh
- -c
- "while true; do date >> /var/log/shared/date.log; sleep 1; done"
Node 자원 3등분해서 pod 생성
문제
You manage a WordPress application. Some pods are not up and running.
Adjust all Pod resource requests as follows:
Divide node resources evenly across all 3 pods.
Give each Pod a fair share of CPU and memory.
Add enough overhead to keep the node stable.
Note - Use exact same requests for both containers and init containers.
Scale down the wordpress deployment to 0 replicas while updating the resource requests.
After updates, confirm: WordPress keeps 3 replicas.
All Pods are running and ready.
k describe node | grep -i capacity A5
# 하지만, 보다보니 capacity가 아닌 Allocatable 값을 3등분하는게 맞는 것 같음
k describe node | grep -i allocatable A5
# requests만 설정!
resources:
requests:
memory: "6774503Ki"
cpu: "3"
kubectl label
리소스에 라벨을 추가할 때 사용
k label pod my-pod app=web // 라벨 추가
k label pod my-pod app=backend --overwrite // 라벨 수정
k label pod my-pod app- // 라벨 삭제
downward api
클러스터 정보를 Pod안으로 전달되는 방식 (downward : 아래쪽으로, 하위로)
Pod안에서 자기 자신의 메타데이터(이름, 네임스페이스, 레이블, CPU 리소스 등)를 환경변수나 파일 형태로 받아볼 수 있는 기능
환경 변수로 넣는 문제가 훨씬 자주 출제됨
- fieldRef : pod의 메타데이터
- resourceFieldRef : 컨테이너가 요청한 리소스 정보(cpu, memory 등)를 가져오는 기능
# 환경 변수
env:
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
env:
- name: CPU_REQUEST
valueFrom:
resourceFieldRef:
resource: requests.cpu
divisor: 1Mi # ← 바이트 대신 Mi 단위로 변환
# 볼륨 파일
volumeMounts:
- name: podinfo
mountPath: /etc/podinfo
readOnly: true
volumes:
- name: podinfo
downwardAPI:
items:
- path: "pod_name"
fieldRef:
fieldPath: metadata.name
Pod TroubleShooting (nodeName, nodeSelector)
0/3 nodes are available:
1 Insufficient cpu,
2 Insufficient memory
# 각 노드별 리소스 확인
k describe nodes | grep -i allocatable -A5
# pod의 requests 확인하고, 노드에서 리소스 감당 못하면 requests 값을 줄인다.
# 또는! pod를 특정 노드에 강제로 띄운다.
nodeName / nodeSelector
- nodeName
- 스케줄러 건너뛰고 해당 노드에 직접 할당
- pod affinity/taint 무시하고 넣어버려서 문제가 좀 있음
- spec: nodeName: node01
- nodeSelector
- 스케줄러가 라벨이 일치하는 노드를 골라서 조건에 맞는 노드에 배치할 수 있게 제한하는 방식
- spec: nodeSelector: kubernetes.io/hostname: node01
Service & Network
Service - expose
Create a service messaging-service to expose the messaging application within the cluster on port 6379.
controlplane ~ ✖ k expose pod messaging --port=6379 --name=messaging-service
kubectl expose 명령어로 Service를 만들 때, Pod의 ContainerPort를 자동으로 targetPort로 사용해준다.
만약 Pod에 containerPort가 따로 없거나 찾을 수 없으면? 그냥 동일하게 targetPort = port 로 지정한다.
따라서 clusterIP 만들때는 expose 명령어 사용하자.
🌟NetworkPolicy
모든 ingress 요청 허용하려면, 아래와 같이 설정한다.
이 외에 networkpolicy 개념 중요하니 많이 알아두기.
ingress:
- ports:
- protocol: TCP
port: 80
NodePort
문제
spline-reticulator 네임스페이스에 있는 기존 front-end Deployment를 수정해서
nginx 컨테이너의 80번 포트(TCP)를 노출하시오.
front-end-svc라는 이름의 새로운 Service를 생성하고, 해당 컨테이너 포트 80/tcp를 노출하시오.
새로 만든 서비스가 NodePort를 통해 개별 Pod에 접근할 수 있도록 구성하시오.
(Configure the new service to also expose the individual pod via a NodePort)
# deployment로 떠있는 ⭐️Pod의 label 확인
# kubectl get pod --show-labels
# deployment 수정해서, 80포트로 설정
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: front-end-svc
spec:
type: NodePort
selector:
app: front-end # spec.template.metadata.labels 기준으로 selector 작성
ports:
- port: 80
targetPort: 80
Headless Service + StatefulSet
고정된 DNS는 Headless Service가 있어야 가능하다 !
headless : ClusterIP없이 파드들을 직접 노출하는 서비스
가상의 IP가 없고, 요청이 바로 파드로 가니까 머리(head)가 없는 것처럼 동작
✅ Stable Network Identities?
StatefulSet의 Pod들이 재시작되거나 재스케쥴되더라도 항상 고정된 네트워크 주소(DNS 이름)을 갖는 것
web-0 → 항상 web-0.web.default.svc.cluster.local
web-1 → 항상 web-1.web.default.svc.cluster.local
kind: StatefulSet
metadata:
name: web
spec:
serviceName: web
replicas: 2
---
kind: Service
metadata:
name: web
spec:
clusterIP: None # 핵심!
Service가 Headless일 경우
- clusterIP: None이면 각 Pod DNS가 직접 노출됨
- Headless Service (clusterIP: None)가 있고, StatefulSet이 해당 서비스를 serviceName:으로 지정
→ CoreDNS가 자동으로 생성해줌
참고) Headless를 사용하는 이유?
StatefulSet을 사용하는 목적은 보통 Kafka, ZooKeeper, MySQL, MongoDB처럼,
→ 노드 간 직접 통신이 필요하고, 서로의 고정 주소를 알아야 하는 시스템에 쓰기 때문
→ 이때 Headless Service가 없으면 DNS 주소가 생기지 않아서 의미가 없음.
⭐️ Ingress to Gateway API
✅ Ingress TLS 는 Gateway에 설정한다.
gateway tls 검색하면,
Gateway API v1.1: Service mesh, GRPCRoute, and a whole lot more 클릭하면 예제있음.
문제
Migrate an existing web application from Ingress to Gateway API.
We must maintain HTTPS access.
A GatewayClass named nginx is installed in the cluster.
First, create a Gateway named web-gateway with hostname gateway.web.k8s.local
that maintains the existing TLS and listener configuration
from the existing ingress resource named web.
Next, create an HTTPRoute named web-route with hostname gateway.web.k8s.local
that maintains the existing routing rules
from the current Ingress resource named web.
You can test your Gateway API configuration with the following command:
curl <https://gateway.web.k8s.local>
Finally, delete the existing Ingress resource named web.
- 참고) 기존 Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
spec:
tls:
- hosts:
- gateway.web.k8s.local
secretName: web-tls
rules:
- host: gateway.web.k8s.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80
Ingress → gateway, httproute 로 변경할 때 hostname 이 있으면 둘 다 일치시켜줘야 제대로 동작
- gateway 생성
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: web-gateway
spec:
gatewayClassName: nginx
listeners:
- name: https
protocol: HTTPS
port: 443
hostname: gateway.web.k8s.local
tls:
certificateRefs:
- kind: Secret
name: web-tls
- HTTPRoute 생성
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: web-route
spec:
parentRefs:
- name: web-gateway
hostnames:
- "gateway.web.k8s.local"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: web-service
port: 80
k get httproute
# web-gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: web-gateway
namespace: cka5673
spec:
gatewayClassName: kodekloud
listeners:
- name: https
protocol: HTTPS
port: 443
hostname: kodekloud.com # 이 도메인으로 오는 요청만 받아라
tls:
certificateRefs:
- name: kodekloud-tls # 여기!
httpRoute
HTTPRoute - hostnames은 어떤 도메인 이름(Host)으로 들어온 요청을 받을지를 설정.
즉, hostnames에 지정한 이름이 아니면, 요청이 무시된다.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: example-route
spec:
hostnames:
- "www.example.com"
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /app
backendRefs:
- name: app-service
port: 80
Ingress
spec:
ingressClassName: nginx # ingress class
rules:
- host: kodekloud-ingress.app # 요청한 host이름. 해당 host일때만 ingress 동작
http:
paths:
- path: / # 요청 url의 경로가 / 로 시작할 때 적용한다.
pathType: Prefix # /로 시작하는 모든 경로를 매칭시킨다.
backend:
service:
name: webapp-svc # 요청을 이 서비스로 넘긴다
port:
number: 80 # 해당 서비스의 80번 포트로 트래픽 넘긴다
스토리지
🌟StorageClass
문제
First, create a new StorageClass named local-path for
an existing provisioner named rancher.io/local-path.
Set the volume binding mode to WaitForFirstConsumer.
Note – Not setting the volume binding mode or
setting it to anything other than WaitForFirstConsumer may result in reduced score.
Next, configure the StorageClass local-path as the default StorageClass.
Note – Do not modify any existing Deployments
or PersistentVolumeClaims. Failure to do so may result in a reduced score.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-path
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer
디폴트 스토리지클래스 설정 방법
- 기존 디폴트 스토리지클래스에서 디폴트 어노테이션 삭제
- 신규 디폴트 스토리지클래스에 디폴트 어노테이션 추가
metadata:
name: my-fast-sc
annotations:
storageclass.kubernetes.io/is-default-class: "true"
PV 복구
문제
A MariaDB Deployment in the mariadb namespace has been deleted by mistake.
Your task is to restore the Deployment ensuring data persistence. Follow the steps:
• Create a PersistentVolumeClaim (PVC) named mariadb in the mariadb namespace
with the following specifications:
Storage 250Mi
• Note - You must use the existing retained PersistentVolume (PV).
There is only one existing PersistentVolume.
Edit the MariaDB Deployment file located at ~/mariadb-deployment.yaml
to use the PVC you created in the previous step.
Apply the updated Deployment file to the cluster.
기존 pv가 retain이면,
pv를 보고 호환되면 pvc 생성. 이때, deployment에서 사용하는 pvc명 동일하게 써도 됨
없으면 deployment에 추가합니다.
volume, configmap - subPath
subPath를 사용하면, configmap 중에서 특정 key 만 파일로 저장할 수 있다.
key 이름은 subPath 값으로 넣어주고, 파일 경로는 mountPath에 지정한다.
❓ my-config ConfigMap의 debug.conf 키만
/etc/config/debug.conf 경로에 파일로 마운트되도록 Pod을 작성하세요.
---
volumeMounts:
- name: config
mountPath: /etc/app/app.conf
subPath: app.conf
volumes:
- name: config
configMap:
name: my-config
출력
🌟explain 결과(명세) 조회
문제
Task:
Verify the cert-manager application which has been deployed in the cluster.
Create a list of all cert-manager Custom Resource Definitions (CRDs) and save it to ~/resources.yaml.
make sure kubectl's default output format and use kubectl to list
CRD's
Do not set an output format.
Failure to do so will result in a reduced score.
Using kubectl, extract the documentation for the subject specification field of the Certificate Custom Resource and save it to ~/subject.yaml.
You may use any output format that kubecl supports.
답
k get crd | grep -i cert-manager > ~/resources.yaml
k explain certificates.spec.subject > ~/subject.yaml
jsonpath 사용
k get nodes -o jsonpath='{.items[*].status.images[*].sizeBytes}'
# 제일 첫번째 range .items[*]는 loop 도는 대상
# 뒤에 있는게 출력 대상
# 아래 두개는 출력 결과가 다름!
k get nodes -o jsonpath='{range .items[*]}{.status.images[*].sizeBytes}{"\\n"}{end}'
k get nodes -o jsonpath='{range .items[*].status.images[*]}{.sizeBytes}{"\\n"}{end}'
트러블 슈팅
journalctl
systemd 리눅스 시스템에서, 시스템 로그를 확인할 수 있게 해주는 명령어
kubelet 상태 확인 시 사용된다.
-u : unit
-n : 100줄까지 보여줘.
journalctl -u kubelet -n 100
Pod
k8s 클러스터는 pod가 스케쥴 되지 않는 이유를 노드 단위로 표현한다.
그래서 PVC 문제여도 노드 리스트(0/2 nodes) 형태로 에러를 보여준다.
- Node 1: "이 Pod를 받을 수 없음 (PVC unbound 때문)"
- Node 2: "이 Pod를 받을 수 없음 (PVC unbound 때문)"
각각의 노드에서 스케줄 실패를 기록해서 최종 출력이 0/2 nodes are available 처럼 보임.
Warning FailedScheduling default-scheduler
0/2 nodes are available: pod has unbound immediate PersistentVolumeClaims.
preemption: 0/2 nodes are available: 2 Preemption is not helpful for scheduling.
# 다른 파드 죽이기 해봐야 소용이 없다. 자원문제가 아니라는 말.
pod가 요구하는 node affinity 조건에 맞지 않는 것 하나
control-plane인데 pod에 toleration이 없다.
그래서 사용 가능한 노드가 없고, 타 자원 삭제는 도움이 안된다 (자원 문제 아님)
0/2 nodes are available:
- 1 node(s) didn't match Pod's node affinity/selector,
- 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }.
preemption: 0/2 nodes are available: 2 Preemption is not helpful for scheduling.
(참고)
에디터 열고 수정했을 때 발생하는 에러
수정한 yaml 내용이 k8s validation 규칙에 맞지 않아서 서버가 거부했다.
왜냐면, pod같은 Immutable 리소스는 특정 필드를 수정할 수 없기 때문. 그래서 수정 불가한 필드를 바꾸거나 했을 때 invalid 에러가 뜸
error: pods "frontend" is invalid
A copy of your changes has been stored to "/tmp/kubectl-edit-2440713918.yaml"
error: Edit cancelled, no valid changes were saved.
Pod가 시작되려고 하는데, kubelet이 ConfigMap이나 Secret을 API 서버에서 가져오지 못했다.
특히, kube-api-access-xxx는 Pod가 ServiceAccount 토큰, CA 인증서 등을 자동으로 mount할 때 사용하는 내부 시스템 볼륨이다.
MountVolume.SetUp failed for volume "kube-api-access-xxxx" :
failed to sync configmap cache: timed out waiting for the condition
kubelet
kubelet은 systemctl로 떠있으니까, journalctl로 로그 봅니다.
journalctl -u kubelet -n 100
# 설정파일 위치 알고싶으면
systemctl cat kubelet
# journalctl로 확인한 정보
# 해당 설정 파일이 없거나 깨져있다.
rror failed to read kubelet config file \\"/var/lib/kubelet/config.yaml\\", error: open /var/lib/kubelet/config.yaml: no such file or directory"
# 해당 파일을 봐야겠군!
🌟 위와 동일하게 파일이 문제였는데, 이 설정값이 비어있어서 notReady 상태였다!
containerRuntimeEndpoint: unix:///run/containerd/containerd.sock
Deployment
상황 : deployment 가 replica 2 로 설정되었으나 아무런 에러없이 그냥 0
rs 도 생성 안돼있음.
ReplicaSet이 생성되지 않은 이유 ?
controlplane 노드에 kube-controller-manager가 죽어 있거나 비정상이다.
왜 kube-controller-manager가 문제인가 ?
Deployment → ReplicaSet 생성 작업은 kube-controller-manager가 담당하는데,
kube-controller-manager가 죽어 있으면, Deployment는 etcd에 등록되지만
ReplicaSet을 만들어주는 작업이 수행되지 않는다.
결국 controller 트러블슈팅
label 변경은 3군데 다 바꾸자.
kubelet
# 이건 꼭 알아야하는 트러블슈팅 포인트로, kubelet이 CRI랑 통신할 때 필요한 설정
container-runtime-endpoint
# staticpod yaml path
staticPodPath: /etc/kubernetes/manifests
api 서버의 포트는 어디서 볼 수 있는가?
# yaml 파일에서 아래 설정!
--secure-port=6443
controlplane:~$ k get pods -n kube-system
The connection to the server 172.30.1.2:6443 was refused - did you specify the right host or port?
ServiceAccount
sa 에서 can-i 쓸때는 —as 뒤를 아래처럼 해야 함
controlplane:~$ kubectl auth can-i create pods --as system:serviceaccount:default:dev-sa
configmap
kubectl set env deployment/cm-webapp -n cm-namespace \\
--from=configmap/app-config
기타 팁 & 명령어
Cluster node join
# CA 키 해시는 아래 명령어 출력 결과에 출력된다.
kubeadm token create --print-join-command
# workernode면 위 출력 명령어 바로 사용
# masternode면 --contol-plane 붙이고, certificate-key 가 주어졌다면 함께 넣는다.
Daemonset
hostPath는 pod가 삭제되어도 그대로 남아있기 때문에, 경로 지정을 신중하게 하자
# node 가 나오는건 hostpath이기 때문이고
# via 는 통해서, 이용해서 라는 뜻.
mount /configurator as HostPath volume on the Node it's running on
write aba997ac-1c89-4d64 into file /configurator/config
on its Node via the command: section
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: configurator
namespace: configurator
spec:
selector:
matchLabels:
name: configurator
template:
metadata:
labels:
name: configurator
spec:
containers:
- name: configurator
image: bash
command:
- sh
- -c
- 'echo aba997ac-1c89-4d64 > /configurator/config && sleep 1d'
volumeMounts:
- name: varlog
mountPath: /configurator
volumes:
- name: varlog
hostPath:
path: /configurator
RBAC
하나의 계정에, 두 개의 binding 이 가능
clusterrole의 쓰임새는 2개이다.
- 클러스터 전체의 권한 관리
- 네임스페이스 내 권한이지만, 여러 네임스페이스에서 공통으로 쓸 권한
✅ Role이나 ClusterRole을 수정하면, 바인딩도 자동으로 따라가니 새로 만들 필요 없음
There are existing Namespaces ns1 and ns2 .
Create ServiceAccount pipeline in both Namespaces.
# clusterrolebinding 으로 전체 클러스터 권한
These SAs should be allowed to view almost everything in the whole cluster. You can use the default ClusterRole view for this.
# rolebinding으로 단일 네임스페이스 권한
These SAs should be allowed to create and delete Deployments in their Namespace.
Verify everything using kubectl auth can-i .
kubectl set
kubectl set selector 명령어를 사용해 서비스의 파드 셀렉터를 변경할 수 있다
# deployment env 추가
kubectl set env deployment/registry STORAGE_DIR=/local
# selector 수정 가능
kubectl set selector svc my-service app=nginx,env=prod
🌟 Pod에서 사용하는 Configmap 수정
여기서 중요한건 configmap 수정하고, 반영하려면 Pod 재시작해야한다. 이건 deployment 이므로 Rollout restart 실행한다.
문제
An NGINX Deploy named nginx-static is running in the nginx-static NS.
It is configured using a CfgMap named nginx-config.
Update the nginx-config CfgMap to allow only TLSv1.3 connections.
Re-create, restart, or scale resources as necessary.
By using command to test the changes:
[candidate@cka2025] $ curl --tls-max 1.2 <https://web.k8s.local>
As TLSv1.2 should not be allowed anymore, the command should fail.
k edit configmap nginx-config -n nginx-static
ssl_protocols TLSv1.3; < 1.2 제거
k rollout restart deployment nginx-static -n nginx-static
kubectl port-forward
Pod나 Service에 있는 포트를 내 로컬 포트와 연결해서 외부 노출 없이 접근할 수 있게 해주는 명령어
언제 사용하는가?
- 클러스터 안에서만 노출된 포트를 테스트하고 싶을 때 : localhost:8080 → pod:80
- CluterIP라 외부 노출이 안될 때, 외부에 노출하지 않고도 접속 가능
k port-forward pod/<pod-name> <local-port>:<pod-port>
k port-forward pod/my-pod 8080:80
이렇게 하면, 내 로컬의 localhost:8080으로 접속하면 클러스터 내 my-pod:80에 연결
Pod외에 서비스에도 가능합니다.
k port-forward svc/my-service 8888:80
Q) “해당 Pod의 80번 포트를 로컬의 8080번으로 연결하여 접근 가능한 상태를 확인하시오.”
“Service가 외부 노출되지 않음. 포트포워딩으로 curl 테스트하시오.”
k port-forwarad pod/my-pod 8080:3000
이렇게 하고 내 로컬에서 localhost:8080 접속했더니, 프로메테우스 창 떴음!
'클라우드' 카테고리의 다른 글
2025 CKA 합격 후기 / 유형 변경 대응법 및 네트워크 문제 경험 공유 (2) | 2025.05.01 |
---|---|
[Calico 01] AS(IRP, ERP)와 BGP란 무엇인가? (0) | 2022.12.03 |
Kubernetes NodePort vs LoadBalancer vs Ingress 비교 (1) | 2022.11.24 |
Learning container from scratch 세미나 정리 (1) | 2022.10.07 |
댓글