티스토리 뷰

반응형

숭실대학교 클라우드 네이티브 과목 실습 정리입니다.
실습 환경: kubeadm 기반 멀티 노드 클러스터 (마스터 1 + 워커 3)
기본 네임스페이스: soongsil

✏️ 목차

  1. 9주차 - Namespace / Pod / Deployment / Service / Node 관리 / k9s / 인증서
  2. 10주차 - Context & RBAC / Volume (EmptyDir · HostPath · NFS)
  3. 11주차 - Volume 정적·동적 프로비저닝 / Service 3종
  4. 12주차 - Ingress 4패턴 / Pod 심화 / ReplicaSet / 롤링 업데이트
  5. 13주차 - Job / CronJob / StatefulSet / DaemonSet
  6. 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

 

 

반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/07   »
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
글 보관함