지난 시간에는 도커 튜토리얼을 통해서 앱을 컨테이너화하여 도커헙에 올리고 docker run으로 실행해보았다. 그리고 파일시스템을 로컬 호스트와 연동하는 방법을 배웠다. 여러 컨테이너는 네트워크로 연결될 수 있으며 예시에서는 mysql와 앱을 연동했다. 이 때 환경변수로 mysql 비밀번호 등을 세팅했다.
이렇게 만들어진 컨테이너는 독립된 앱으로서 기능한다. 그런데 만약 트래픽이 커져서 1개의 컨테이너로 트래픽을 감당하기 어려운 상황이 오면? 어떻게 스케일업할 것인지 로드밸런싱은 어떻게 할 것인지 문제다.
그래서 여러 개의 컨테이너를 자동적으로 관리해주는 소프트웨어가 등장했고 그러한 소프트웨어를 컨테이너를 관리, 지휘한다는 의미에서 컨테이너 오케스트레이션 이라고 부른다.
이제부터는 이 쿠버네티스의 기본개념을 학습하고 여러 실습을 진행해볼 것이다.
공식 문서나 여러 블로그 글에도 이러한 용어들이 공통적으로 등장하니 한번 훝어보고 이후에 용어가 헷갈릴 때 마다 찾아와 지속적으로 상기시켜야한다.
쿠버네티스를 배포하게 되면 쿠버네티스 클러스터가 형성된다라고 표현한다.
클러스터는 모든 컴포넌트를 포괄하는 추상적인 개념이다.
쿠버네티스 소프트웨어를 구성하는 컨트롤플레인, 노드 등과 같은 모든 구성요소가 이 클러스터 안에 있다고 할 수 있다.

로컬에서 연습할 때는 minikube를 사용하는데, minikube는 로컬에 VM을 만들고 그 안에 클러스터를 만들어준다. 원한다면 클러스터 내에 여러 노드를 생성할 수 있기도 하다. ex.) minikube start node —2
클러스터의 전반적인 상태를 관리하는 역할을 하는 컴포넌트이다.
예를 들면 새로운 파드를 자동으로 생성하거나 작업을 스케줄링할 수 있다. 내부적으로 아래와 같은 요소들이 있다. 마스터 노드라고도 한다.
kube-apiserver
etcd
kube-scheduler
kube-controller-manager
컨테이너가 파드 형태로 띄워지는 공간이다. 내부 구성요소는 아래와 같다.
kubelet
kube-proxy
컨테이너 런타임
쿠버네티스에서 생성하고 관리할 수 있는 어플리케이션 최소 단위
쿠버네티스에서 구동되는 애플리케이션이다. 여러 가지 내장 워크로드 리소스를 제공한다.
워크로드는 OS에서 프로세스가 동작하는 일련의 행위를 뜻한다.
OS와 비교를 하자면, 쿠버네티스가 OS역할을 하고 파드가 프로세스라고 할 수 있다. 이러한 파드를 동작시키기 위해 내장된 리소스들이 워크로드 리소스인 것이다.
ReplicaSet
Deployment
예시
apiVersion: apps/v1
kind: Deployment
metadata:
name: todo-app
spec:
replicas: 3
selector:
matchLabels:
app: todo-app
template:
metadata:
labels:
app: todo-app
spec:
containers:
- name: node-app
image: rookie0031/getting-started
workingDir: /app
command: ["sh", "-c"]
args:
- "yarn install && yarn run dev"
ports:
- containerPort: 3000
env:
- name: MYSQL_HOST
value: "mysql-jisu" # Use the correct MySQL service name or DNS name
- name: MYSQL_USER
value: "root"
- name: MYSQL_PASSWORD
value: "6aePpn5x2M" # Use the correct MySQL password
- name: MYSQL_DB
value: "my_database"
StatefulSet
예시
apiVersion: apps/v1
kind: StatefulSet
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"apps/v1","kind":"StatefulSet","metadata":{"annotations":{},"labels":{"app.kubernetes.io/component":"primary","app.kubernetes.io/instance":"mysql-jisu","app.kubernetes.io/managed-by":"Helm","app.kubernetes.io/name":"mysql","app.kubernetes.io/version":"8.0.34","helm.sh/chart":"mysql-9.12.3"},"name":"mysql-jisu","namespace":"wrtn-app"},"spec":{"podManagementPolicy":"","replicas":1,"selector":{"matchLabels":{"app.kubernetes.io/component":"primary","app.kubernetes.io/instance":"mysql-jisu","app.kubernetes.io/name":"mysql"}},"serviceName":"mysql-jisu","template":{"metadata":{"annotations":{"checksum/configuration":"a92f02ed0db1396c24e06793b9f6f130c0ca105b679e4826298249c80228095c"},"labels":{"app.kubernetes.io/component":"primary","app.kubernetes.io/instance":"mysql-jisu","app.kubernetes.io/managed-by":"Helm","app.kubernetes.io/name":"mysql","app.kubernetes.io/version":"8.0.34","helm.sh/chart":"mysql-9.12.3"}},"spec":{"affinity":{"nodeAffinity":null,"podAffinity":null,"podAntiAffinity":{"preferredDuringSchedulingIgnoredDuringExecution":[{"podAffinityTerm":{"labelSelector":{"matchLabels":{"app.kubernetes.io/instance":"mysql-jisu","app.kubernetes.io/name":"mysql"}},"topologyKey":"kubernetes.io/hostname"},"weight":1}]}},"containers":[{"env":[{"name":"BITNAMI_DEBUG","value":"false"},{"name":"MYSQL_ROOT_PASSWORD","valueFrom":{"secretKeyRef":{"key":"mysql-root-password","name":"mysql-jisu"}}},{"name":"MYSQL_DATABASE","value":"my_database"}],"envFrom":null,"image":"docker.io/bitnami/mysql:8.0.34-debian-11-r56","imagePullPolicy":"IfNotPresent","livenessProbe":{"exec":{"command":["/bin/bash","-ec","password_aux=\"${MYSQL_ROOT_PASSWORD:-}\"\nif [[ -f \"${MYSQL_ROOT_PASSWORD_FILE:-}\" ]]; then\n password_aux=$(cat \"$MYSQL_ROOT_PASSWORD_FILE\")\nfi\nmysqladmin status -uroot -p\"${password_aux}\"\n"]},"failureThreshold":3,"initialDelaySeconds":5,"periodSeconds":10,"successThreshold":1,"timeoutSeconds":1},"name":"mysql","ports":[{"containerPort":3306,"name":"mysql"}],"readinessProbe":{"exec":{"command":["/bin/bash","-ec","password_aux=\"${MYSQL_ROOT_PASSWORD:-}\"\nif [[ -f \"${MYSQL_ROOT_PASSWORD_FILE:-}\" ]]; then\n password_aux=$(cat \"$MYSQL_ROOT_PASSWORD_FILE\")\nfi\nmysqladmin status -uroot -p\"${password_aux}\"\n"]},"failureThreshold":3,"initialDelaySeconds":5,"periodSeconds":10,"successThreshold":1,"timeoutSeconds":1},"resources":{"limits":{},"requests":{}},"securityContext":{"runAsNonRoot":true,"runAsUser":1001},"startupProbe":{"exec":{"command":["/bin/bash","-ec","password_aux=\"${MYSQL_ROOT_PASSWORD:-}\"\nif [[ -f \"${MYSQL_ROOT_PASSWORD_FILE:-}\" ]]; then\n password_aux=$(cat \"$MYSQL_ROOT_PASSWORD_FILE\")\nfi\nmysqladmin status -uroot -p\"${password_aux}\"\n"]},"failureThreshold":10,"initialDelaySeconds":15,"periodSeconds":10,"successThreshold":1,"timeoutSeconds":1},"volumeMounts":[{"mountPath":"/bitnami/mysql","name":"data"},{"mountPath":"/opt/bitnami/mysql/conf/my.cnf","name":"config","subPath":"my.cnf"}]}],"initContainers":null,"securityContext":{"fsGroup":1001},"serviceAccountName":"mysql-jisu","volumes":[{"configMap":{"name":"mysql-jisu"},"name":"config"}]}},"updateStrategy":{"type":"RollingUpdate"},"volumeClaimTemplates":[{"metadata":{"labels":{"app.kubernetes.io/component":"primary","app.kubernetes.io/instance":"mysql-jisu","app.kubernetes.io/name":"mysql"},"name":"data"},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"8Gi"}}}}]}}
creationTimestamp: "2023-10-26T12:28:26Z"
generation: 5
labels:
app.kubernetes.io/component: primary
app.kubernetes.io/instance: mysql-jisu
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: mysql
app.kubernetes.io/version: 8.0.34
helm.sh/chart: mysql-9.12.3
name: mysql-jisu
namespace: wrtn-app
resourceVersion: "407711"
uid: 840aa6b1-af92-4741-addc-f5844ac5e4d8
spec:
persistentVolumeClaimRetentionPolicy:
whenDeleted: Retain
whenScaled: Retain
podManagementPolicy: OrderedReady
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app.kubernetes.io/component: primary
app.kubernetes.io/instance: mysql-jisu
app.kubernetes.io/name: mysql
serviceName: mysql-jisu
template:
metadata:
annotations:
checksum/configuration: a92f02ed0db1396c24e06793b9f6f130c0ca105b679e4826298249c80228095c
creationTimestamp: null
labels:
app.kubernetes.io/component: primary
app.kubernetes.io/instance: mysql-jisu
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: mysql
app.kubernetes.io/version: 8.0.34
helm.sh/chart: mysql-9.12.3
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/instance: mysql-jisu
app.kubernetes.io/name: mysql
topologyKey: kubernetes.io/hostname
weight: 1
containers:
- env:
- name: BITNAMI_DEBUG
value: "false"
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
key: mysql-root-password
name: mysql-jisu
- name: MYSQL_DATABASE
value: my_database
image: docker.io/bitnami/mysql:8.0.34-debian-11-r56
imagePullPolicy: IfNotPresent
livenessProbe:
exec:
command:
- /bin/bash
- -ec
- |
password_aux="${MYSQL_ROOT_PASSWORD:-}"
if [[ -f "${MYSQL_ROOT_PASSWORD_FILE:-}" ]]; then
password_aux=$(cat "$MYSQL_ROOT_PASSWORD_FILE")
fi
mysqladmin status -uroot -p"${password_aux}"
failureThreshold: 3
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
name: mysql
ports:
- containerPort: 3306
name: mysql
protocol: TCP
readinessProbe:
exec:
command:
- /bin/bash
- -ec
- |
password_aux="${MYSQL_ROOT_PASSWORD:-}"
if [[ -f "${MYSQL_ROOT_PASSWORD_FILE:-}" ]]; then
password_aux=$(cat "$MYSQL_ROOT_PASSWORD_FILE")
fi
mysqladmin status -uroot -p"${password_aux}"
failureThreshold: 3
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
resources: {}
securityContext:
runAsNonRoot: true
runAsUser: 1001
startupProbe:
exec:
command:
- /bin/bash
- -ec
- |
password_aux="${MYSQL_ROOT_PASSWORD:-}"
if [[ -f "${MYSQL_ROOT_PASSWORD_FILE:-}" ]]; then
password_aux=$(cat "$MYSQL_ROOT_PASSWORD_FILE")
fi
mysqladmin status -uroot -p"${password_aux}"
failureThreshold: 10
initialDelaySeconds: 15
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /bitnami/mysql
name: data
- mountPath: /opt/bitnami/mysql/conf/my.cnf
name: config
subPath: my.cnf
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext:
fsGroup: 1001
serviceAccount: mysql-jisu
serviceAccountName: mysql-jisu
terminationGracePeriodSeconds: 30
volumes:
- configMap:
defaultMode: 420
name: mysql-jisu
name: config
updateStrategy:
type: RollingUpdate
volumeClaimTemplates:
- apiVersion: v1
kind: PersistentVolumeClaim
metadata:
creationTimestamp: null
labels:
app.kubernetes.io/component: primary
app.kubernetes.io/instance: mysql-jisu
app.kubernetes.io/name: mysql
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 8Gi
volumeMode: Filesystem
status:
phase: Pending
status:
availableReplicas: 1
collisionCount: 0
currentReplicas: 1
currentRevision: mysql-jisu-86cd485794
observedGeneration: 5
readyReplicas: 1
replicas: 1
updateRevision: mysql-jisu-86cd485794
updatedReplicas: 1
PersistentVolume
예시
kind: PersistentVolume
metadata:
annotations:
hostPathProvisionerIdentity: 9948a7ee-6671-403d-8a41-bfe641742451
pv.kubernetes.io/provisioned-by: k8s.io/minikube-hostpath
creationTimestamp: "2023-10-26T12:28:26Z"
finalizers:
- kubernetes.io/pv-protection
name: pvc-45e9fc59-e4fe-41bd-a928-4ab546825b0d
resourceVersion: "404741"
uid: 9f7b2608-9a6b-4929-90f7-d0ba26feb3dc
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 8Gi
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
name: data-mysql-jisu-0
namespace: wrtn-app
resourceVersion: "404731"
uid: 45e9fc59-e4fe-41bd-a928-4ab546825b0d
hostPath:
path: /tmp/hostpath-provisioner/wrtn-app/data-mysql-jisu-0
type: ""
persistentVolumeReclaimPolicy: Delete
storageClassName: standard
volumeMode: Filesystem
status:
phase: Bound
배포된 서비스를 네트워크 서비스로 외부에 노출시키고 트래픽을 관리하는 객체이다.
클러스터 내 서비스에 대한 외부 접근을 관리하는 API 오브젝트이다.
클러스터 외부에서 내부 서비스로 HTTP와 HTTPS 경로를 노출한다. 외부 트래픽이 파드에 전달되는 과정은 아래와 같다.


쿠버네티스 공식문서
쿠버네티스 오브젝트 설명
서비스 설명