06. 쿠버네티스 핵심 개념 (1)
이 문서는 인프런 강의 "데브옵스를 위한 쿠버네티스 마스터"을 듣고 작성되었습니다. 최대한 요약해서 강의 내용을 최소로 하는데 목표를 두고 있어서, 더 친절하고 정확한 내용을 원하신다면 강의를 구매하시는 것을 추천드립니다. => 강의 링크
큐브 시스템 컴포넌트란
큐브 시스템 컴포넌트란 쿠버네티스(kubenetes) 시스템을 구성하는 중요 컴포넌트입니다. 그림으로 나타내면 다음과 같다.
마스터 노드(Control Plane Node)는 다음과 같이 구성되어 있다.
- etcd : 모든 클러스터 데이터를 담는 쿠버네티스의 저장소이다.
- kube-apiserver : Kubernetes API를 노출한다. 모든 컴포넌트는 이 컴포넌트를 통해서 통신한다.
- kube-scheduler : 생성된 Pod를 어떤 Node에 배치하고 실핼할 것인지를 선택한다.
- kube-controller-manager : 실제 리소스를 관리하는 컴포넌트 다음과 같이 크게 4가지로 구성된다.
- 노드 컨트롤러 : 노드 상태 관리
- 레플리케이션 컨트롤러 : Pod 개수를 조절
- 엔드포인트 컨트롤러 : Service와 Pod을 연결
- 서비스 어카운트 & 토큰 컨트롤러 : 인증/인가를 담당
슬레이브 노드(Data Node)는 다음과 같이 구성되어 있다.
- kubelet : 마스터 노드의 명령에 따라서 CRT(Container Run Time - Docker)를 관리한다.
- kube-proxy : 노드 - 컨테이너, 컨테이너-컨테이너간 통신을 담당한다.
이번 장에서는 VM
에서 실습을 진행한다. 마스터 노드에서 다음 명령어를 실행해보자.
$ kubectl get pod -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-558bd4d5db-5z4jv 1/1 Running 1 5d
coredns-558bd4d5db-g9nhg 1/1 Running 1 5d
etcd-master 1/1 Running 2 5d
kube-apiserver-master 1/1 Running 2 5d
kube-controller-manager-master 1/1 Running 2 5d
kube-proxy-hvpz5 1/1 Running 2 5d
kube-proxy-kdtb5 1/1 Running 1 5d
kube-proxy-qthkm 1/1 Running 1 5d
kube-scheduler-master 1/1 Running 2 5d
weave-net-46bsq 2/2 Running 3 5d
weave-net-fgxbv 2/2 Running 4 5d
weave-net-qr5v6 2/2 Running 5 5d
-n
옵션은 네임스페이스를 지정하는 옵션이다. Kubernetes
클러스터를 구성하면 기본적으로 큐브 시스템이 뒤에서 Pod
의 형태로 돌아가게 된다. etcd
, kube-apiserver
, kube-controller-manager
, kube-scheduler
가 동작되는 것을 확인할 수 있다.
이제 /etc/kubernetes/manifests/
경로로 이동해서 어떤 파일들을 가지고 있는지 확인해보자.
$ cd /etc/kubernetes/manifests/
$ ll
total 24
drwxr-xr-x 2 root root 4096 7월 3 18:50 ./
drwxr-xr-x 4 root root 4096 7월 3 18:50 ../
-rw------- 1 root root 2126 7월 3 18:50 etcd.yaml
-rw------- 1 root root 3947 7월 3 18:50 kube-apiserver.yaml
-rw------- 1 root root 3350 7월 3 18:50 kube-controller-manager.yaml
-rw------- 1 root root 1384 7월 3 18:50 kube-scheduler.yaml
이 디렉토리에 존재하는 파일을 가지고 마스터 노드에서 동작하는 큐브 시스템에 대한 커스텀한 설정을 할 수 있다는 것을 알아두자. 이번엔 슬레이브 노드에서 다음을 입력해보자.
$ ps -ef | grep "kube"
root 698 1 1 19:06 ? 00:00:18 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --network-plugin=cni --pod-infra-container-image=k8s.gcr.io/pause:3.4.1
root 2562 2523 0 19:07 ? 00:00:00 /usr/local/bin/kube-proxy --config=/var/lib/kube-proxy/config.conf --hostname-override=slave2
root 3174 2796 0 19:07 ? 00:00:00 /home/weave/kube-utils -run-reclaim-daemon -node-name=slave2 -peer-name=6e:85:ad:af:a7:bb -log-level=debug
gurumee 11764 5641 0 19:25 pts/0 00:00:00 grep --color=auto kube
위의 명령어 두 줄을 보면, kubelet
과 kube-proxy
가 실행되고 있음을 확인할 수 있다.
Etcd란?
Etcd
는 Kubernetes
클러스터의 저장소로 사용되며, 원래는 키-값 쌍으로 데이터를 저장하는 NoSQL
데이터베이스이다. 분산 환경에서 자주 쓰인다고 한다.
그냥 어떻게 쓰는지 확인하기 위해서 이를 간단하게 조작할 수 있는 etcdctl
을 설치한다.
# 압축 파일 다운로드
$ wget https://github.com/etcd-io/etcd/releases/download/v3.5.0/etcd-v3.5.0-linux-amd64.tar.gz
# 압축 파일 해제
$ tar -xf etcd-v3.5.0-linux-amd64.tar.gz
이제 압축된 파일로 들어가보자. etcd
, etcdctl
등이 설치된 것을 확인할 수 있다.
$ cd etcd-v3.5.0-linux-amd64
$ ll
total 56312
drwxr-xr-x 3 gurumee gurumee 4096 6월 16 06:51 ./
drwxrwxr-x 3 gurumee gurumee 4096 7월 8 19:44 ../
drwxr-xr-x 3 gurumee gurumee 4096 6월 16 06:51 Documentation/
-rwxr-xr-x 1 gurumee gurumee 23560192 6월 16 06:51 etcd*
-rwxr-xr-x 1 gurumee gurumee 17969152 6월 16 06:51 etcdctl*
-rwxr-xr-x 1 gurumee gurumee 16048128 6월 16 06:51 etcdutl*
-rw-r--r-- 1 gurumee gurumee 42066 6월 16 06:51 README-etcdctl.md
-rw-r--r-- 1 gurumee gurumee 7359 6월 16 06:51 README-etcdutl.md
-rw-r--r-- 1 gurumee gurumee 9394 6월 16 06:51 README.md
-rw-r--r-- 1 gurumee gurumee 7896 6월 16 06:51 READMEv2-etcdctl.md
etcd
바이너리 파일을 이용해서 실행할 수 있지만 이미 kubernetes
클러스터를 운영하면서 실행되고 있는 상태이다. 클러스터가 이용하고 있는 etcd
를 이용해서 키를 조회할 수 있다. 터미널에 다음을 입력한다.
$ sudo ETCDCTL_API=3 ./etcdctl --endpoints 127.0.0.1:2379 --cacert /etc/kubernetes/pki/etcd/ca.crt --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key get / --prefix --keys-only
...
/registry/services/endpoints/default/kubernetes
/registry/services/endpoints/kube-system/kube-dns
/registry/services/specs/default/kubernetes
/registry/services/specs/kube-system/kube-dns
그럼 가지고 있는 키 목록이 쭉 나열될 것이다. 위 명령어에서 뒤에 get
부터가 실제 명령어다. 이제 이 etcd
에 키-값 쌍을 한 번 저장해보고 꺼내보도록 하자 매우. 쉽다. 먼저 터미널에 다음을 입력한다.
$ sudo ETCDCTL_API=3 ./etcdctl --endpoints 127.0.0.1:2379 --cacert /etc/kubernetes/pki/etcd/ca.crt --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key put test foo
OK
키 "test" 값 "foo"를 etcd
에 저장했다. 이를 가져와보자. 터미널에 다음을 입력한다.
$ sudo ETCDCTL_API=3 ./etcdctl --endpoints 127.0.0.1:2379 --cacert /etc/kubernetes/pki/etcd/ca.crt --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key get test
test
foo
첫 번째 줄에 키가, 두 번째 줄에 값이 출력될 수 있음을 확인할 수 있다. 우리는 etcd
가 키-값 쌍으로 저장하는 데이터베이스이며 kubernetes
가 저장소로 사용하는 것만 알고 있으면 된다.
Pod이란?
Pod
이란 Kubernetes
과 관리하는 가장 기본이 되는 요소이다.
위 그림처럼 노드 내부에 생성이 된다. 단, 노드에 걸쳐서 Pod
이 생성되지는 않는다. 하나의 Pod
에는 여러 컨테이너가 실행될 수 있으나 모니터링 등의 이유가 아니라면 1 Pod 1 Container
가 원칙이다. 또한 Pod
은 NAT
로 구성된 것 처럼 외부와 격리되어 있다. 외부와 연결하려면 Service
가 필요하다.
Pod
은 명령어로 만들 수 있지만 보통 yaml
파일로 작성하는 것이 일반적이다. 다음과 같이 simple-app-pod-v1.yaml
을 작성해보자.
src/ch06/simple-app-pod-v1.yaml
apiVersion: v1
kind: Pod
metadata:
name: simple-app
spec:
containers:
- name: simple-app
image: gurumee92/simple-app
ports:
- containerPort: 8080
그 후 터미널에 다음을 입력해보자.
$ kubectl create -f simple-app-pod-v1.yaml
job.batch/simple-app created
그 다음 다음 명령어로 잘 실행이 되나 확인을 해보자.
$ kubectl get pod -w
NAME READY STATUS RESTARTS AGE
simple-app-5r4fb 0/1 ContainerCreating 0 3s
simple-app-5r4fb 1/1 Running 0 4s
실행이 되면 kubectl describe
명령어를 통해서 더 자세한 내용을 살펴볼 수 있다.
$ kubectl describe pod simple-app-zkh2m
Name: simple-app-zkh2m
Namespace: default
Priority: 0
Node: slave2/10.0.2.5
Start Time: Thu, 08 Jul 2021 20:13:08 +0900
Labels: controller-uid=06d6338a-c41d-4a88-a6c4-fef094c539e0
job-name=simple-app
Annotations: <none>
Status: Running
IP: 10.32.0.3
IPs:
IP: 10.32.0.3
Controlled By: Job/simple-app
Containers:
simple-app:
Container ID: docker://20b5995bc8c7339f79740c7a6391b313a789f672dd527b97590598ed1b4f6dca
Image: gurumee92/simple-app
Image ID: docker-pullable://gurumee92/simple-app@sha256:baf83add38ca5429adb80edc8e1647179d1771e853e00ae95c274bccc3b0dcd1
Port: 8080/TCP
Host Port: 0/TCP
State: Running
Started: Thu, 08 Jul 2021 20:15:40 +0900
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-xh6pc (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
kube-api-access-xh6pc:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 7m36s default-scheduler Successfully assigned default/simple-app-zkh2m to slave2
Normal Pulling 7m35s kubelet Pulling image "gurumee92/simple-app"
Normal Pulled 5m4s kubelet Successfully pulled image "gurumee92/simple-app" in 2m31.03551003s
Normal Created 5m4s kubelet Created container simple-app
Normal Started 5m4s kubelet Started container simple-app
이미지 정보, 어떤 노드에 생성되었는지, 컨테이너 실행 이력 등 여러 정보를 확인할 수 있다. 이제 포트 포워딩을 통해서 외부로 노출시켜보자
$ kubectl port-forward simple-app-zkh2m 8080:8080
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
Handling connection for 8080
Handling connection for 8080
E0708 20:17:48.868587 66425 portforward.go:385] error copying from local connection to remote stream: read tcp4 127.0.0.1:8080->127.0.0.1:36710: read: connection reset by peer
E0708 20:17:48.876150 66425 portforward.go:385] error copying from local connection to remote stream: read tcp4 127.0.0.1:8080->127.0.0.1:36712: read: connection reset by peer
Handling connection for 8080
E0708 20:17:52.900618 66425 portforward.go:385] error copying from local connection to remote stream: read tcp4 127.0.0.1:8080->127.0.0.1:36726: read: connection reset by peer
Handling connection for 8080
터미널을 새로 열어 다음을 입력해보자.
$ curl localhost:8080
Hello World simple-app-zkh2
굳! 그 후 삭제는 다음으로 할 수 있다.
$ kubectl delete -f simple-app-pod-v1.yaml
job.batch "simple-app" deleted
Probe란?
Probe
란 컨테이너에서 kubelet
에 의해 주기적으로 수행되는 자가진단 및 회복을 작업을 뜻한다. 다음과 같이 세 가지의 Probe
가 있다.
- livenessProbe : 컨테이너가 동작 중인지 여부를 나타냄. 실패하면 컨테이너를 죽이고 다시 재시작.
- readinessProbe : 컨테이너가 요청을 처리할 준비가 되었는지 여부를 나타냄. 만약 실패하면, 로드 밸런서에서 해당 Pod의 IP 주소를 제거함.
- startupProbe : 컨테이너 내 애플리케이션이 시작 여부를 나타냄. 이 프로브가 활성화되면 나머지 두 프로브는 비활성화됨. 실패하면 멐ㄴ테이너를 죽이고 재시작함.
여기서는 HTTP 엔드 포인트에 대한 헬스 체크를 하는 livenessProbe
를 간단하게 작성해보자. 다음을 작성한다.
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-http
spec:
containers:
- name: liveness
image: k8s.gcr.io/liveness
args:
- /server
livenessProbe:
httpGet:
path: /healthz
port: 8080
httpHeaders:
- name: Custom-Header
value: Awesome
initialDelaySeconds: 3
periodSeconds: 3
이제 터미널에 다음을 입력해서 Pod
을 만든다.
$ kubectl create -f http-liveness.yaml
그 후 Pod
을 쭉 관찰해보자.
$ kubectl get pod -w
NAME READY STATUS RESTARTS AGE
liveness-http 1/1 Running 0 6s
liveness-http 1/1 Running 1 24s
liveness-http 1/1 Running 2 45s
liveness-http 1/1 Running 3 65s
liveness-http 0/1 CrashLoopBackOff 3 81s
liveness-http 1/1 Running 4 112s
liveness-http 1/1 Running 5 2m12s
liveness-http 0/1 CrashLoopBackOff 5 2m28s
...
처음 빼놓고 약 20초 간격으로 재시작하는 것을 확인할 수 있다. 그렇게 3번을 실행하면 Pod
이 종료된다. 그 후 다시 재시작한다. 이게 계속 반복된다.
실제 k8s.gcr.io/liveness
이미지로 작성된 컨테이너는 다음 코드가 들어있다.
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
duration := time.Now().Sub(started)
if duration.Seconds() > 10 {
w.WriteHeader(500)
w.Write([]byte(fmt.Sprintf("error: %v", duration.Seconds())))
} else {
w.WriteHeader(200)
w.Write([]byte("ok"))
}
})
위 코드는 시작 시 처음 10초간 200 상태코드를 내뱉다가 그 이후부터는 500 상태 코드를 내뱉는 것이다. 그래서 계속 10초 이후에 에러로 판단해서 계속 Pod
을 종료했다가 재시작하게 되는 것이다. 다시 한 번 기억하자. 이런 자가 진단 작업 + 자가 회복 과정이 Probe
이다.
Label이란?
레이블이란 모든 리소스를 구성하는 매우 간단하면서도 강력한 쿠버네티스의 기능이다. 키-값 쌍으로 리소스에 레이블을 지정해서 부가적인 정보를 나타낼 수 있다. 조회/추가/수정/삭제가 가능하며 심지어 리소스가 생성된 이후에도 추가/수정/삭제를 할 수는 있지만 권장하지는 않는다.
보통 이런식으로 붙여서 레이블 단위로 리소스들을 관리할 수 있다.
또한 사내에 정책이 없다면 다음 레이블들의 사용을 고려해보자.
이제 직접 레이블을 만들어보자. 다음과 같이 simple-app-pod-v2.yaml
을 만들어보자.
src/ch06/simple-app-pod-v2.yaml
apiVersion: v1
kind: Pod
metadata:
name: simple-app-v2
labels:
creation_method: manual
env: prod
spec:
containers:
- name: simple-app
image: gurumee92/simple-app
ports:
- containerPort: 8080
여기서 metadata
필드 밑에 labels
하위 필드들이 모두 레이블들이다. 그리고 이제 실습을 위해서 v1, v2를 모두 생성해두자.
# v1 생성
$ kubectl create -f simple-app-pod-v1.yaml
pod/simple-app created
# v2 생성
$ kubectl create -f simple-app-pod-v2.yaml
pod/simple-app-v2 created
# pod 조회
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
simple-app 1/1 Running 0 3m32s 10.32.0.3 slave2 <none> <none>
simple-app-v2 1/1 Running 0 3m27s 10.32.0.4 slave2 <none> <none>
이제 레이블을 검색해보자.
$ kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
simple-app 1/1 Running 0 8m37s <none>
simple-app-v2 1/1 Running 0 8m32s creation_method=manual,env=prod
v1에는 어떤 레이블도 확인되지 않고 v2에 레이블들이 붙은 것을 확인할 수 있다. 이제 v1에 레이블을 추가해보자.
# v1에 label 추가 env=test, creation_method=manual
$ kubectl label pod simple-app env=test creation_method=manual
pod/simple-app labeled
# 라벨 확인
$ kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
simple-app 1/1 Running 0 10m creation_method=manual,env=test
simple-app-v2 1/1 Running 0 10m creation_method=manual,env=prod
그 후, v2에 creation_method의 값을 automation으로 변경해보자.
# v2 라벨 업데이트
$ kubectl label pod simple-app-v2 creation_method=automation
error: 'creation_method' already has a value (manual), and --overwrite is false
이렇게 에러가 뜬다. 수정하려면 --overwrite
옵션을 켜주어야 한다. 다시 한 번 다음과 같이 입력해보자.
# v2 라벨 업데이트
$ kubectl label pod simple-app-v2 creation_method=automation --overwrite
pod/simple-app-v2 labeled
# 업데이트 내용 확인
$ kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
simple-app 1/1 Running 0 13m creation_method=manual,env=test
simple-app-v2 1/1 Running 0 13m creation_method=automation,env=prod
이제 v2의 레이블 creation_method를 제거해보자. 터미널에 다음을 입력한다.
# v2 라벨 제거
$ kubectl label pod simple-app-v2 creation_method-
pod/simple-app-v2 labeled
# 라벨 확인 v2에 creation_method 제거됨
$ kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
simple-app 1/1 Running 0 18m creation_method=manual,env=test
simple-app-v2 1/1 Running 0 18m env=prod
또한 레이블을 필터링해서 검색할 수 있다. 먼저 터미널에 다음을 입력한다.
$ kubectl get pod -l creation_method
NAME READY STATUS RESTARTS AGE
simple-app 1/1 Running 0 24m
위의 명령어는 creation_method
레이블이 붙어 있는 Pod
들을 필터링해서 보여준다. 이와는 반대로 다음과 같이 작성도 가능하다.
$ kubectl get pod -l '!creation_method'
NAME READY STATUS RESTARTS AGE
simple-app-v2 1/1 Running 0 25m
그럼 조건이 반전되서 creation_method
레이블이 붙지 않은 Pod
들만 필터링해서 볼 수 있다. 위의 명령어들은 레이블 유무로 필터링했다면, 아래 명령어를 이용하면 다음과 같이 레이블 값 별로 필터링할 수 있다.
$ kubectl get pod -l 'env=prod'
NAME READY STATUS RESTARTS AGE
simple-app-v2 1/1 Running 0 26m
레이블을 여러 조건들로 필터링하고 싶다면, 다음과 같이 작성할 수 있다.
$ kubectl get pod -l "env=test,creation_method=manual"
NAME READY STATUS RESTARTS AGE
simple-app 1/1 Running 0 28m