티스토리 뷰
숭실대학교 클라우드 네이티브 과목 실습 정리입니다.
실습 환경: kubeadm 기반 멀티 노드 클러스터 (마스터 1 + 워커 3)
기본 네임스페이스: soongsil
✏️ 목차
- 9주차 - Namespace / Pod / Deployment / Service / Node 관리 / k9s / 인증서
- 10주차 - Context & RBAC / Volume (EmptyDir · HostPath · NFS)
- 11주차 - Volume 정적·동적 프로비저닝 / Service 3종
- 12주차 - Ingress 4패턴 / Pod 심화 / ReplicaSet / 롤링 업데이트
- 13주차 - Job / CronJob / StatefulSet / DaemonSet
- 14주차 - ConfigMap / Secret / Helm Chart
✏️ 9주차 - Namespace / Pod / Deployment / Service / Node 관리 / k9s / 인증서
Namespace
클러스터 안에서 리소스를 논리적으로 격리하는 가상 공간입니다. 팀별, 환경(dev/prod)별 분리에 활용하며, 네임스페이스를 삭제하면 포함된 모든 리소스도 함께 삭제되므로 주의가 필요합니다.
$ kubectl create ns ssu-namespace
$ kubectl get ns
$ kubectl describe ns ssu-namespace
$ kubectl delete ns soongsil
yaml 파일로 생성하는 방법은 다음과 같습니다.
apiVersion: v1
kind: Namespace
metadata:
name: soongsil
$ kubectl create -f soongsil.yaml
Pod / Deployment / Service
| 리소스 | 설명 |
| Pod | 컨테이너 실행의 기본 단위. 단독 배포 시 삭제하면 재생성 없이 사라짐 |
| Deployment | ReplicaSet을 통해 파드 개수를 보장. 파드 삭제 시 자동 재생성 |
| Service | 파드에 접근하기 위한 고정 엔드포인트 제공 |
# 파드 생성 및 관리
$ kubectl run ssu-nginx --image=nginx:1.25 -n soongsil
$ kubectl get pods -n soongsil
$ kubectl describe pod ssu-nginx -n soongsil
$ kubectl logs -f ssu-nginx -n soongsil
# 디플로이먼트 생성 (레플리카 3개)
$ kubectl create deploy ssu-nginx2 --image=nginx:1.26 --port=80 --replicas=3 -n soongsil
# 서비스 노출 (NodePort)
$ kubectl expose pod ssu-nginx --type=NodePort --port 80 --name=ssu-nginx -n soongsil
$ kubectl expose deployment ssu-nginx2 --type=NodePort --port 80 --name=ssu-nginx2 -n soongsil
# 리소스 삭제
$ kubectl delete pod ssu-nginx -n soongsil
$ kubectl delete deployment ssu-nginx2 -n soongsil
$ kubectl delete svc ssu-nginx ssu-nginx2 -n soongsil
Node 관리 - Cordon / Drain / Taint
Cordon / Uncordon
특정 노드에 새 파드가 스케줄되지 않도록 막습니다. 기존 파드는 그대로 유지됩니다.
$ kubectl cordon jjy-cluster2
$ kubectl get nodes # STATUS: Ready,SchedulingDisabled 확인
$ kubectl uncordon jjy-cluster2
Drain
노드의 파드를 다른 노드로 이동시킵니다. Drain 적용 시 Cordon이 자동으로 걸리므로 완료 후 반드시 Uncordon 해야 합니다.
$ kubectl drain jjy-cluster4 --ignore-daemonsets --force --delete-emptydir-data
$ kubectl uncordon jjy-cluster4
Taint / Toleration
Taint가 걸린 노드에는 해당 Taint를 허용하는(Toleration) 파드만 배포됩니다. 마스터 노드는 기본적으로 NoSchedule Taint가 적용되어 있습니다.
# 마스터 노드 taint 확인
$ kubectl describe node jjy-cluster1 | grep -i taint
Taints: node-role.kubernetes.io/control-plane:NoSchedule
# 마스터 노드에도 배포하려면 Toleration 추가
tolerations:
- key: "node-role.kubernetes.io/control-plane"
operator: "Equal"
effect: "NoSchedule"
k9s 설치
터미널에서 쿠버네티스 클러스터를 시각적으로 관리할 수 있는 CLI 도구입니다.
$ mkdir k9s; cd k9s
$ wget https://github.com/derailed/k9s/releases/download/v0.26.7/k9s_Linux_x86_64.tar.gz
$ tar zxvf k9s_Linux_x86_64.tar.gz
$ sudo mv k9s /usr/local/bin/k9s
사용자 인증서 발급 (OpenSSL)
개인키 → CSR → CRT 순서로 발급합니다.
# 1. 개인키(key) 생성
$ openssl genrsa -out ssu-user.key 2048
# 2. 인증서 서명 요청(CSR) 생성
$ openssl req -new -key ssu-user.key \
-subj "/CN=ssu-user/O=Kubernetes Korea Group" -out ssu-user.csr
# 3. CA로 서명하여 인증서(CRT) 발급
$ sudo openssl x509 -req -in ssu-user.csr \
-CA /etc/kubernetes/ssl/ca.crt \
-CAkey /etc/kubernetes/ssl/ca.key \
-CAcreateserial -out ssu-user.crt -days 10000
✏️ 10주차 - Context & RBAC / Volume
Context & RBAC
RBAC(Role-Based Access Control)은 역할(Role)을 정의하고 사용자/서비스어카운트에 바인딩하는 접근 제어 방식입니다.
$ kubectl create role ssu-role --verb=get --verb=list --verb=watch \
--resource=pods --namespace=ssu-namespace
$ kubectl create serviceaccount ssu-serviceaccount --namespace=ssu-namespace
$ kubectl create rolebinding ssu-rolebinding \
--role=ssu-role \
--serviceaccount=ssu-namespace:ssu-serviceaccount \
--user=ssu-user -n ssu-namespace
# 사용자 인증 정보 등록 및 컨텍스트 생성
$ kubectl config set-credentials ssu-user \
--client-certificate=ssu-user.crt --client-key=ssu-user.key --embed-certs=true
$ kubectl config set-context ssu-context \
--cluster=cluster-host --user=ssu-user --namespace=ssu-namespace
$ kubectl config use-context ssu-context
# 조회 및 삭제
$ kubectl config get-contexts
$ kubectl config delete-context ssu-context
$ kubectl config delete-user ssu-user
Volume - EmptyDir
파드 내 컨테이너 간에 임시 데이터를 공유하는 볼륨입니다. 파드가 삭제되면 데이터도 함께 소멸됩니다.
apiVersion: v1
kind: Pod
metadata:
name: ssu-emptydir-volume
spec:
volumes:
- name: ssu-log-volume
emptyDir: {}
containers:
- name: ssu-log-writer
image: busybox
command: ["/bin/sh", "-c"]
args:
- while true; do echo "$(date)" >> /var/log/ssu.log; sleep 5; done
volumeMounts:
- name: ssu-log-volume
mountPath: /var/log
- name: ssu-log-reader
image: busybox
command: ["/bin/sh", "-c"]
args:
- tail -f /var/log/ssu.log
volumeMounts:
- name: ssu-log-volume
mountPath: /var/log
Volume - HostPath
워커 노드의 특정 디렉터리를 컨테이너에 마운트합니다. nodeSelector로 배포할 노드를 지정해야 합니다.
volumes:
- name: ssu-log-volume
hostPath:
path: /var/log/ssu-logs
type: DirectoryOrCreate
nodeSelector:
kubernetes.io/hostname: jjy-cluster4
Volume - NFS (PV / PVC)
| 개념 | 설명 |
| StorageClass | 스토리지 종류 및 프로비저너 정의 |
| PersistentVolume (PV) | 실제 스토리지 리소스 (관리자가 생성) |
| PersistentVolumeClaim (PVC) | 파드가 스토리지를 요청하는 오브젝트 |
$ kubectl create -f nfs-volume.yaml
$ kubectl get storageclass
$ kubectl get pv
$ kubectl get pvc
✏️ 11주차 - Volume 정적·동적 프로비저닝 / Service 3종
Volume - 정적 프로비저닝
관리자가 PV를 미리 생성해 두고, PVC가 조건(용량, accessMode)에 맞는 PV를 자동으로 바인딩합니다.
# 워커 노드에 디렉터리 사전 생성
$ sudo ssh ubuntu@192.168.0.72 -i ~/.ssh/soongsil-key.pem
$ sudo mkdir -p /data/volumes/pv1 && sudo chmod 777 /data/volumes/pv1
apiVersion: v1
kind: PersistentVolume
metadata:
name: ssu-pv1
spec:
storageClassName: ssu-storageclass
persistentVolumeReclaimPolicy: Delete
capacity:
storage: 1G
accessModes:
- ReadWriteOnce
local:
path: /data/volumes/pv1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- jjy-cluster2
Volume - 동적 프로비저닝
PVC 생성 시 Provisioner가 자동으로 PV를 생성합니다. NFS와 Helm Chart를 활용합니다.
$ helm repo add nfs-subdir-external-provisioner \
https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner
$ helm install ssu-dynamic-provisioner \
nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
-f values.yaml --version 4.0.18
# PVC 생성만 하면 PV 자동 생성됨
$ kubectl create -f dynamic-pvc.yaml
$ kubectl get pv
Service 타입 3종
| 타입 | 설명 | 접속 방법 |
| NodePort | 노드 포트(30000~32767)로 외부 노출 | {NODE_IP}:{NODE_PORT} |
| ClusterIP | 클러스터 내부 전용. 기본 타입 | 서비스 이름으로 DNS 접근 |
| LoadBalancer | MetalLB 등으로 EXTERNAL-IP 할당 | 외부 고정 IP 제공 |
# NodePort 서비스 생성
$ kubectl expose deployment ssu-nodeport-deploy \
--type=NodePort --port 80 --name=ssu-nodeport-svc -n soongsil
# ClusterIP 내부 통신 테스트
$ kubectl run -it ssu-pod-test --image=posquit0/doraemon bash -n soongsil
/ # curl ssu-clusterip-svc:8080
# LoadBalancer yaml
apiVersion: v1
kind: Service
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- port: 80
targetPort: 80
✏️12주차 - Ingress 4패턴 / Pod 심화 / ReplicaSet / 롤링 업데이트
Ingress - 4가지 라우팅 패턴
클러스터 외부의 HTTP/HTTPS 트래픽을 내부 서비스로 라우팅하는 리소스입니다.
| 패턴 | 설명 |
| 실습1 Host/Path 단일 | 단일 도메인 + 단일 경로 → 하나의 서비스 |
| 실습2 IP/Paths | IP 기반으로 여러 경로(/, /hello, /httpd) → 각 서비스 |
| 실습3 Host/Paths | 하나의 도메인에 여러 경로 → 각 서비스 |
| 실습4 Hosts/Path | 서비스마다 다른 서브도메인 (hello.IP.nip.io, grafana.IP.nip.io) |
# 실습1 — CLI로 빠르게 생성
$ kubectl create ingress ssu-ing --class=nginx \
--rule="ing.133.186.220.251.nip.io/=ssu-ing-svc:80" -n soongsil
# 실습3 — Host/Paths yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ssu-host-paths-ing
namespace: soongsil
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: ssu.133.186.220.251.nip.io
http:
paths:
- path: /hello
pathType: Prefix
backend:
service:
name: ssu-hello-svc
port:
name: http
- path: /
pathType: Prefix
backend:
service:
name: ssu-grafana-svc
port:
name: http
Pod 심화
환경변수(ENV) 주입
env:
- name: SOONGSIL
value: "Platform as a service!"
command: ["/bin/sh"]
args: ["-c", "while true; do echo $(SOONGSIL); sleep 10; done"]
멀티 컨테이너 파드
spec:
containers:
- image: nginx
name: ssu-container1
- image: redis
name: ssu-container2
- image: memcached
name: ssu-container3
ReplicaSet
파드 개수를 항상 지정된 수로 유지합니다. 일반적으로 Deployment를 통해 간접적으로 사용합니다.
$ kubectl create -f rs.yaml
$ kubectl scale replicaset ssu-rs --replicas 5 -n soongsil
Deployment - 롤링 업데이트 / 롤백
# 이미지 롤링 업데이트
$ kubectl set image deployment ssu-rolling-up-deploy nginx=nginx:1.15 \
--record -n soongsil
# 히스토리 조회
$ kubectl rollout history deployment ssu-rolling-up-deploy -n soongsil
# 직전 버전으로 롤백
$ kubectl rollout undo deployment ssu-rolling-up-deploy -n soongsil
# 특정 리비전으로 롤백
$ kubectl rollout undo deployment ssu-rolling-up-deploy --to-revision 3 -n soongsil
✏️ 13주차 - Job / CronJob / StatefulSet / DaemonSet
Job - 일회성 작업 실행
작업이 성공적으로 완료되면 파드가 종료됩니다.
| 종류 | 설명 |
| 단일잡 | 파드 1개 실행 후 완료 |
| 다중잡 | completions: 3 → 파드를 순차적으로 3번 실행 |
| 병렬잡 | completions: 3, parallelism: 3 → 파드 3개를 동시에 실행 |
$ kubectl create job ssu-job --image=busybox -n soongsil -- date
$ kubectl get job -n soongsil
$ kubectl logs ssu-job-4dzml -n soongsil
# 단일잡 yaml
apiVersion: batch/v1
kind: Job
metadata:
name: ssu-single-job
namespace: soongsil
spec:
template:
spec:
containers:
- name: ssu-container
image: busybox
command: ["echo", "Platform as a service!"]
restartPolicy: Never
backoffLimit: 2
# 병렬잡 추가 필드
spec:
completions: 3
parallelism: 3
CronJob - 스케줄 기반 반복 작업
리눅스 cron 표현식으로 정기 실행합니다. 매 실행마다 Job 오브젝트와 파드가 새로 생성됩니다.
$ kubectl create cronjob ssu-cronjob \
--image=busybox --schedule="*/1 * * * *" -n soongsil -- date
$ kubectl get cronjob -n soongsil
apiVersion: batch/v1
kind: CronJob
metadata:
name: ssu-cronjob4
namespace: soongsil
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: ssu-container
image: kubetm/init
command: ["sh", "-c", "echo 'start'; sleep 20; echo 'end'"]
terminationGracePeriodSeconds: 0
successfulJobsHistoryLimit: 2
failedJobsHistoryLimit: 1
StatefulSet - 상태 있는 앱 배포
파드마다 고유한 이름(ssu-sts-0, ssu-sts-1, ssu-sts-2)과 네트워크 ID를 부여하며 순서대로 생성·삭제합니다. DB 같은 상태 있는 애플리케이션에 적합합니다.
Deployment 파드는 랜덤 해시 이름을 가지지만, StatefulSet은 순번이 고정됩니다.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: ssu-sts
namespace: soongsil
spec:
serviceName: "ssu-service" # Headless Service 이름 (고유 DNS 부여)
replicas: 3
updateStrategy:
type: OnDelete
selector:
matchLabels:
app: nginx
$ kubectl get sts -n soongsil
$ kubectl get pod -n soongsil
# ssu-sts-0, ssu-sts-1, ssu-sts-2 순서대로 생성 확인
DaemonSet - 모든 노드에 1개씩 배포
클러스터의 모든 노드에 파드를 1개씩 자동 배포합니다. 로그 수집기(fluentd), 모니터링 에이전트 등에 활용합니다.
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: ssu-daemonset
namespace: kube-system
spec:
selector:
matchLabels:
name: fluentd-elasticsearch
template:
spec:
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: ssu-container
image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
volumeMounts:
- name: ssu-log
mountPath: /var/log
volumes:
- name: ssu-log
hostPath:
path: /var/log
$ kubectl get ds -n kube-system
# DESIRED=4 → 노드 수(마스터1 + 워커3)와 동일
✏️ 14주차 - ConfigMap / Secret / Helm Chart
ConfigMap vs Secret 비교
| 항목 | ConfigMap | Secret |
| 용도 | 비민감 설정값 (URL, 이름, 플래그) | 민감 정보 (비밀번호, 토큰, 인증서) |
| 저장 방식 | 평문 | Base64 인코딩 |
| 주입 방식 | env / envFrom / Volume 마운트 | env / envFrom / Volume 마운트 |
ConfigMap
실습1 - 특정 키를 env로 주입 (configMapKeyRef)
$ kubectl create cm ssu-cm --from-literal=SOONGSIL="Soongsil University" -n soongsil
env:
- name: SSU_ENV
valueFrom:
configMapKeyRef:
name: ssu-cm
key: SOONGSIL
실습2 - 전체 키를 envFrom으로 주입 (configMapRef)
$ kubectl create configmap ssu-cm2 \
--from-literal=SOONGSIL="SSU" \
--from-literal=PaaS="Platform as a service!" -n soongsil
envFrom:
- configMapRef:
name: ssu-cm2
실습3 - yaml 파일로 ConfigMap 생성
apiVersion: v1
kind: ConfigMap
metadata:
name: ssu-cm3
namespace: soongsil
data:
SOONGSIL: "Soongsil University"
Secret
실습1 - 특정 키를 env로 주입 (secretKeyRef)
$ kubectl create secret generic ssu-secret \
--from-literal=SOONGSIL="Soongsil University" -n soongsil
env:
- name: SOONGSIL_ENV
valueFrom:
secretKeyRef:
name: ssu-secret
key: SOONGSIL
실습2 - yaml로 Secret 생성 + envFrom
yaml로 직접 작성할 때는 값을 Base64로 인코딩해야 합니다.
$ echo -n "Soongsil University" | base64
U29vbmdzaWwgVW5pdmVyc2l0eQ==
apiVersion: v1
kind: Secret
metadata:
name: ssu-secret2
namespace: soongsil
type: Opaque
data:
SOONGSIL: U29vbmdzaWwgVW5pdmVyc2l0eQ==
PaaS: UGxhdGZvcm0gYXMgYSBTZXJ2aWNl
envFrom:
- secretRef:
name: ssu-secret2 # 파드에서는 자동으로 디코딩되어 주입됨
실습3 - Volume으로 Secret 마운트 (파일로 접근)
각 키가 파일명, 값이 파일 내용으로 지정 경로에 저장됩니다. 인증서나 설정 파일 형태로 전달할 때 사용합니다.
volumeMounts:
- name: ssu-volume
mountPath: "/secrets"
readOnly: true
volumes:
- name: ssu-volume
secret:
secretName: ssu-secret3
$ kubectl exec -it ssu-secret-pod3 -n soongsil -- sh
/ # cat /secrets/PAAS
Platform as a service!
Helm Chart
여러 쿠버네티스 리소스를 하나의 패키지로 묶어 관리·배포합니다. Go 템플릿 문법을 사용합니다.
디렉토리 구조
my-chart/
├── Chart.yaml # 차트 메타데이터 (이름, 버전, 설명)
├── values.yaml # 변수 기본값 정의
└── templates/ # k8s yaml 템플릿 파일 모음
핵심 템플릿 문법
{{ .Values.pod.name }} # values.yaml 값 참조
{{ $.Values.global.namespace }} # with 블록 안에서 루트 접근
{{ $.Release.Namespace | quote }} # 릴리즈 네임스페이스 참조
{{- with .Values.pod }} # 특정 scope로 컨텍스트 전환
name: {{ .name }}
{{- end }}
{{- range $val := .helloPaths }} # 리스트 순회
- path: {{ $val.path }}
{{- end }}
Helm 주요 명령어
$ helm install {RELEASE_NAME} {CHART_DIR} -n {NAMESPACE}
$ helm list -n {NAMESPACE}
$ helm get manifest {RELEASE_NAME} -n {NAMESPACE} # 렌더링된 yaml 확인
$ helm get all {RELEASE_NAME} -n {NAMESPACE}
$ helm uninstall {RELEASE_NAME} -n {NAMESPACE}
실습1 - 멀티 컨테이너 파드
# values.yaml
pod:
name: ssu-multi-container
nginxImage: nginx
redisImage: redis
memcachedImage: memcached
# templates/pod.yaml
{{- with .Values.pod }}
apiVersion: v1
kind: Pod
metadata:
name: {{ .name }}
namespace: {{ $.Release.Namespace | quote }}
spec:
containers:
- image: {{ .nginxImage }}
name: {{ $.Values.global.containerName }}1
- image: {{ .redisImage }}
name: {{ $.Values.global.containerName }}2
- image: {{ .memcachedImage }}
name: {{ $.Values.global.containerName }}3
{{- end }}
$ helm install multi-container multi-container-chart -n soongsil
$ helm uninstall multi-container -n soongsil
실습2 - ConfigMap 실습3을 Helm으로 배포
# templates/cm.yaml
{{- with .Values.configmap }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .name }}
namespace: {{ $.Values.global.namespace }}
data:
SOONGSIL: {{ .data }}
{{- end }}
$ helm install cm-pod cm-pod-chart -n soongsil
$ kubectl logs ssu-cm-pod3 -n soongsil
# → Soongsil University
실습3 - Deployment + Service + Ingress 패키징
grafana·hello·httpd 서비스 전체를 하나의 Helm Chart로 배포합니다.
# ingress.yaml 핵심 부분
{{- range $val := .helloPaths }}
- path: {{ $val.path }}
pathType: {{ $val.pathType }}
backend:
service:
name: {{ $val.service }}
port:
name: {{ $val.portName }}
{{- end }}
$ helm install ing-deploy-svc ing-deploy-svc-chart
$ kubectl get deploy,svc,ing -n soongsil
$ helm uninstall ing-deploy-svc
'Back-End > 개인 공부' 카테고리의 다른 글
| [K8s / 쿠버네티스] 쿠버네티스 MinIO 버킷 데이터 Mac으로 다운로드하기 (0) | 2026.05.31 |
|---|---|
| [Spring Boot / 스프링 부트] 축제 부스앱 결제 자동화 도전기 - Portone 반려부터 RT PAY까지 (2) | 2026.05.18 |
| [Spring Boot / 스프링 부트] Spring Boot의 Command 객체 (0) | 2026.05.17 |
| Nginx + SSL + DNS (0) | 2026.04.30 |
| 무중단 배포 방식 정리 - Rolling, Blue-Green, Canary (0) | 2026.03.30 |
- Total
- Today
- Yesterday
- DP
- 카운팅 정렬
- 스택
- 자바
- BFS
- js
- 자료구조
- 우선순위 큐
- 이분 매칭
- 유니온 파인드
- 에라토스테네스의 체
- 백준
- 투 포인터
- 유클리드 호제법
- 자바스크립트
- Do it!
- 백준 풀이
- html
- HTML5
- 스프링 부트 crud 게시판 구현
- DFS
- 알고리즘 공부
- java
- C++
- 반복문
- 세그먼트 트리
- 알고리즘
- C++ Stack
- c++ string
- CSS
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
