안녕하세요. 이번 포스팅에서는 쿠버네티스의 기본 용어와 명령어들을 한번 알아보겠습니다.
저는 3대의 가상 이미지를 띄워서 클러스터를 구성했습니다.
실습은 "한 권으로 배우는 도커&쿠버네티스" 책을 참고해서 진행했습니다.
1. 쿠버네티스 구성
쿠버네티스는 마스터노드와 워커노드로 구성되어 있습니다. 마스터노드는 클라이언트의 API 요청을 받고 워커 노드에 명령을 내리는 역할을 합니다. 명령을 받은 워커노드는 실제 컨테이너를 실행하는 역할을 하게 됩니다.
우선 설치된 쿠버네티스 클러스터의 정보부터 확인해보겠습니다.
> kubectl get nodes
NAME STATUS ROLES AGE VERSION
ubun20-01 Ready control-plane 154d v1.28.5
ubun20-02 Ready control-plane 154d v1.28.5
ubun20-03 Ready control-plane 154d v1.28.5
node들의 정보는 위와 같습니다. ansible로 설치한 클러스터로, 설치되면 3개의 노드가 control-plane인 것을 확인할 수 있습니다.
kubectl cluster-info라는 명령어를 통해 클러스터의 정보도 가져올 수 있습니다.
> kubectl cluster-info
Kubernetes control plane is running at https://192.168.64.11:6443
저는 마스터노드를 11번 서버에 설정했기 때문에 cluster-info 명령어를 통해 11번 서버에서 control plane이 running 중이라는 상태를 확인할 수 있습니다.
참고로 마스터노드를 설정하는 방법은 로컬호스트에 저장된 kube config의 내용을 설정해주면 됩니다.
vi ~/.kube/config
해당 파일의 clusters요소에 마스터노드의 같은 경로에 있는 파일의 내용을 넣어주면 됩니다. 서버의 ip는 마스터노드로 설정할 노드의 ip를 적어줍니다.
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: 생략
name: control-cluster
쿠버네티스 클러스터에서 실행중인 pod의 목록을 확인하려면, kubectl get pod 명령어를 실행합니다.
보다 자세한 결과를 확인하고 싶으면, -o wide 옵션을 사용합니다.
위에서 실행했던 get nodes 명령어에서도 -o wide 옵션을 사용하면 자세한 결과를 확인할 수 있습니다.
> kubectl get pod
NAME READY STATUS RESTARTS AGE
metallb-controller-665d96757f-dnnlz 1/1 Running 0 15s
metallb-speaker-l8spq 4/4 Running 54 (12m ago) 60d
metallb-speaker-p5gxk 4/4 Running 42 (12m ago) 60d
metallb-speaker-p5rgm 4/4 Running 40 (12m ago) 60d
nginx-hello-846f4bdb6d-2wl86 1/1 Running 1 (12m ago) 2d18h
nginx-hello-846f4bdb6d-b8x6l 1/1 Running 0 15s
nginx-hello-846f4bdb6d-fz5kg 1/1 Running 1 (12m ago) 2d18h
nginx-hello-846f4bdb6d-hl7wc 1/1 Running 1 (12m ago) 2d18h
nginx-hello-846f4bdb6d-jb6jq 1/1 Running 0 15s
nginx-hello-846f4bdb6d-tg44q 1/1 Terminating 2 (12m ago) 2d18h
nginx-hello-846f4bdb6d-wqjfp 1/1 Terminating 2 (12m ago) 2d18h
> kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
metallb-controller-665d96757f-dnnlz 1/1 Running 0 74s 10.233.109.152 ubun20-02 <none> <none>
metallb-speaker-l8spq 4/4 Running 54 (13m ago) 60d 192.168.64.12 ubun20-02 <none> <none>
metallb-speaker-p5gxk 4/4 Running 42 (13m ago) 60d 192.168.64.13 ubun20-03 <none> <none>
metallb-speaker-p5rgm 4/4 Running 40 (13m ago) 60d 192.168.64.11 ubun20-01 <none> <none>
nginx-hello-846f4bdb6d-2wl86 1/1 Running 1 (13m ago) 2d18h 10.233.104.210 ubun20-01 <none> <none>
nginx-hello-846f4bdb6d-b8x6l 1/1 Running 0 74s 10.233.109.153 ubun20-02 <none> <none>
nginx-hello-846f4bdb6d-fz5kg 1/1 Running 1 (13m ago) 2d18h 10.233.104.209 ubun20-01 <none> <none>
nginx-hello-846f4bdb6d-hl7wc 1/1 Running 1 (13m ago) 2d18h 10.233.104.213 ubun20-01 <none> <none>
nginx-hello-846f4bdb6d-jb6jq 1/1 Running 0 74s 10.233.109.157 ubun20-02 <none> <none>
nginx-hello-846f4bdb6d-tg44q 1/1 Terminating 2 (13m ago) 2d18h 10.233.70.34 ubun20-03 <none> <none>
nginx-hello-846f4bdb6d-wqjfp 1/1 Terminating 2 (13m ago) 2d18h 10.233.70.31 ubun20-03 <none> <none>
실행중인 pod를 삭제할때는 delete 명령을 실행합니다. kubectl delete {pod 이름}
pod를 실행하는 방법에는 명령어에는 run이 있습니다. run 명령어를 통해 hello-world라는 pod를 실행해보겠습니다.
> kubectl run hello-world --image=hello-world --restart=Always pod/hello-world created
pod/hello-world created
> kubectl get pod
NAME READY STATUS RESTARTS AGE
hello-world 0/1 CrashLoopBackOff 1 (4s ago) 11s
이렇게 run 명령어로 pod를 실행할 수 있습니다. 생성된 pod는 delete 명령어로 삭제합니다. 다음으로는 manifest(매니페스트)를 통한 pod를 생성하는 방법이 있습니다. manifest는 yaml 확장자 파일을 이용해 pod를 생성하는 방법입니다. 일반 run 명령어보다 설정할 수있는 옵션이 많고, 한눈에 직관적으로 알아볼 수 있다는 장점이 있습니다.
nginx application을 생성하는 pod를 yaml형식의 manifest파일을 통해 생성해보겠습니다.
참고로 vi로 터미널에서 직접 수정할 수 있지만, 저는 vscode의 익스텐션을 통해 조금 더 간단하게 매니페스트를 작성합니다.
extension에서 yaml를 검색하고 위와 같은 익스텐션을 설치합니다.
설치가 됐으면 톱니바퀴를 클릭하고, Extension Settings(확장 설정)을 클릭합니다.
스크롤을 밑으로 내리다보면 나오는 Edit in settings.json 버튼을 클릭해줍니다.
settings.json에 아래와 같이 kubernetes 관련 설정을 추가해줍니다.
{
"yaml.schemas": {
"kubernetes": "*.yaml"
}
}
만약 json파일 내에 다른 설정이 되어있다면 첫 depth에 위 구문을 넣어주면 됩니다.
설정이 완료됐다면 아래와 같은 자동완성 기능이 활성화되는 것을 볼 수 있습니다.
다시 돌아와서 매니페스트를 이용한 pod 생성에 대해 설명드리겠습니다.
apiVersion: v1
kind: Pod
metadata:
name: nginx01
spec:
containers:
- name: nginx-test01
image: nginx:latest
yaml파일을 통해 pod를 띄울때는 apply 명령어를 사용합니다.
> kubectl apply -f nginx-test.yml
pod/nginx01 created
> kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx01 1/1 Running 0 10s
yaml 파일을 통해 생성한 pod는 생성할 때와 마찬가지로 f 옵션을 준뒤 delete 명령어를 통해 삭제합니다.
> kubectl delete -f nginx-test.yml
pod "nginx01" deleted
2. 디플로이먼트
디플로이먼트(deployment)는 파드의 개수를 관리합니다. 파드는 레플리카셋이라는 수치를 통하여 파드의 개수를 설정하고 유지합니다. 디플로이먼트는 이런 파드의 레플리카셋을 관리하고 스펙을 정의하는 기능을 합니다. 또한 생성되고 제거되는 pod의 이력을 관리하는 기능을 가지고 있어, 단순히 pod를 생성하고 레플리카셋을 설정하는 것보다 디플로이먼트를 통해 pod를 관리합니다.
디플로이먼트도 kubectl 명령어를 통해 생성할 수 있습니다. pod는 run 명령어를 통해 생성했다면, 디플로이먼트는 create 명령어를 통해 생성합니다.
> kubectl create deployment deploy-hello --image=hello-world
deployment.apps/deploy-hello created
생성된 디플로이먼트는 아래의 명령어들로 확인할 수 있습니다.
> kubectl get pod
NAME READY STATUS RESTARTS AGE
deploy-hello-7c478bcd59-fsc8p 0/1 Completed 3 (33s ago) 57s
> kubectl get replicaset
NAME DESIRED CURRENT READY AGE
deploy-hello-7c478bcd59 1 1 0 2m6s
> kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
deploy-hello 0/1 1 0 2m20s
생성된 디플로이먼트의 삭제는 pod와 마찬가지로 delete 명령어를 통해 수행합니다.
> kubectl delete deployment deploy-hello
deployment.apps "deploy-hello" deleted
다음으로 디플로이먼트를 실행할 때, 레플리카셋을 조정하는 방법을 알아보겠습니다.
레플리카셋이란, pod의 개수를 원하는 개수만큼 유지시켜 줄 때의 개수를 말합니다. 예를 들어 레플리카셋을 3개로 지정하면 쿠버네티스 클러스터는 해당 디플로이먼트를 통해 생성된 pod의 개수를 항상 3개로 유지시키려고 합니다.
nginx이미지를 통해 레플리카셋이 3인 pod를 생성해보겠습니다.
> kubectl create deployment deploy-nginx --image=nginx --replicas=3
deployment.apps/deploy-nginx created
생성된 pod를 확인해보면, READY 컬럼에 3개의 pod가 관리되고 있음을 알 수 있습니다.
> kubectl get pod
NAME READY STATUS RESTARTS AGE
deploy-nginx-7f979874cf-7qp6w 1/1 Running 0 51s
deploy-nginx-7f979874cf-s7lnt 1/1 Running 0 51s
deploy-nginx-7f979874cf-tdj7w 1/1 Running 0 51s
디플로이먼트와 레플리카셋도 확인해보면 3개의 pod를 관리하고 있음을 확인할 수 있습니다.
> kubectl get deployment,replicaset
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/deploy-nginx 3/3 3 3 99s
NAME DESIRED CURRENT READY AGE
replicaset.apps/deploy-nginx-7f979874cf 3 3 3 99s
레플리카셋이 3개로 설정된 pod를 자세히 살펴보겠습니다. -o wide 옵션을 통해 pod의 상태를 더 자세히 볼 수 있습니다.
> kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
deploy-nginx-7f979874cf-qwhs2 1/1 Running 0 30s 10.233.70.41 ubun20-03 <none> <none>
deploy-nginx-7f979874cf-skn2j 1/1 Running 0 30s 10.233.70.39 ubun20-03 <none> <none>
deploy-nginx-7f979874cf-vc4mv 1/1 Running 0 30s 10.233.104.225 ubun20-01 <none> <none>
3개의 pod는 각각 10.233.70.41, 10.233.70.39, 10.233.104.225의 IP로 실행됐으며, 위에 두개는 3번 노드에, 아래 한개는 1번 노드에 실행되고 있음을 확인할 수 있습니다. 해당 IP로 띄워진 pod는 클러스터 내부에서만 통신이 가능하고, 현재는 pod를 외부로 노출시키는 작업을 하지 않았기 때문에 로컬호스트에서 해당 IP주소로 접속해보면 접속이 되지 않는 것을 확인할 수 있습니다.
쿠버네티스 클러스터의 서버에 접속해서 nc 명령어를 통해 네트워크 통신을 확인해보면 정상적으로 통신이 되는 것을 확인할 수 있습니다.
> nc -zv 10.233.70.39 80
Connection to 10.233.70.39 80 port [tcp/http] succeeded!
여기서 생성된 pod 중 하나를 삭제해보겠습니다.
> kubectl delete pod deploy-nginx-7f979874cf-qwhs2
pod "deploy-nginx-7f979874cf-qwhs2" deleted
그다음 다시 pod를 확인해보면 아래와 같은 결과를 확인할 수 있습니다.
> kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
deploy-nginx-7f979874cf-jcdd5 1/1 Running 0 7s 10.233.70.42 ubun20-03 <none> <none>
deploy-nginx-7f979874cf-skn2j 1/1 Running 0 4m27s 10.233.70.39 ubun20-03 <none> <none>
deploy-nginx-7f979874cf-vc4mv 1/1 Running 0 4m27s 10.233.104.225 ubun20-01 <none> <none>
위에서 제거한 qwhs2의 id를 가진 pod가 사라지고 새로운 pod가 생겼습니다.(AGE가 7초) 이렇듯 replicaset이 설정된 deployment는 pod가 어떤 요인에 의해 삭제되면 다른 pod를 생성하여 개수를 유지하려는 성질이 있습니다.
이제 명령어를 통해 생성한 디플로이먼트를 삭제하고 매니페스트를 통해 새로운 디플로이먼트를 생성해보겠습니다.
> kubectl delete deployment deploy-nginx
deployment.apps "deploy-nginx" deleted
> kubectl get deployment
No resources found in default namespace.
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-nginx
spec:
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: nginx-deploy
template:
metadata:
labels:
app.kubernetes.io/name: nginx-deploy
spec:
containers:
- name: nginx
image: nginx:latest
위 구문에서 처음 나오는 spec은 디플로이먼트에 대한 상태를 정의합니다. 그 안에 있는 또다른 spec은 pod의 상태를 정의하는 것으로 여기서 유의할 점은 디플로이먼트의 matchLabels와 pod의 labels의 이름을 같도록 지정해주어야 합니다.
이제 생성한 매니페스트를 통해 디플로이먼트를 실행시켜보겠습니다.
> kubectl apply -f nginx-deploy.yaml
deployment.apps/deploy-nginx created
생성된 디플로이먼트를 확인해보겠습니다.
> kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
deploy-nginx-5b6549999b-m2f69 1/1 Running 0 5s 10.233.109.166 ubun20-02 <none> <none>
deploy-nginx-5b6549999b-qmvhp 1/1 Running 0 5s 10.233.104.223 ubun20-01 <none> <none>
deploy-nginx-5b6549999b-zkpld 1/1 Running 0 5s 10.233.70.45 ubun20-03 <none> <none>
레플리카셋이 3개인 디플로이먼트가 잘 실행된 것을 확인할 수 있습니다. 위에서 생성한 yaml파일을 수정하면서 레플리카셋과 이미지의 버전 등을 변경해줄 수 있습니다. metadata의 이름을 같게 설정하면 기존에 생성된 디플로이먼트의 상태를 업데이트 해주는 용도로 사용할 수 있습니다.
3. 서비스
다음으로 서비스에 대해 알아보겠습니다. 서비스는 pod의 외부 접근을 위한 기능을 수행합니다. pod와 디플로이먼트 절에서 확인했듯이 IP주소는 pod가 재생성되면 재할당되는 특성이 있습니다. 이때, 클라이언트는 pod에 접속하기 위해 갖고 있던 IP주소가 변경되었기 때문에 pod로 요청이 정상적으로 이루어지지 않을 수 있습니다. 이런 문제를 해결하기 위해 서비스를 사용합니다. 서비스는 클라이언트의 요청을 받아서 pod로 포워딩해주는 역할을 합니다.
서비스는 이런 특성을 활용해서 외부 트래픽 노출, 로드밸런싱, 서비스에 대한 디스커버리 등의 기능을 수행합니다.
+-----------------------+
| Client |
+-----------+-----------+
|
v
+-----------+-----------+
| Service |
+-----------+-----------+
|
+-------------+--------------+
| | |
v v v
+---+---+ +---+---+ +---+---+
| Pod | | Pod | | Pod |
|(App 1)| |(App 2)| |(App 3)|
+---+---+ +---+---+ +---+---+
쿠버네티스 서비스의 종류에는 ClusterIP, NodePort, LoadBalancer, ExternalName이 있습니다. 서비스의 종류는 매니페스트에서 type으로 지정할 수 있습니다. 이제 서비스 종류 각각에 대해 알아보겠습니다.
(1) ClusterIp
cluster ip는 서비스의 default 값으로, 클러스터 내에서만 pod에 접근할 수 있도록 합니다.
cluster ip를 설정하면, 클러스터 내에서만 접근 가능한 IP를 할당하고, 외부에서는 접근할 수 없습니다.
cluster ip를 설정하는 매니페스트를 작성해보겠습니다.
서비스 실습은 위에서 작성했던 디플로이먼트의 web-deploy먼트를 활용해서 진행하겠습니다.
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
selector:
app.kubernetes.io/name: nginx-deploy
type: ClusterIP
ports:
- protocol: TCP
port: 80
디플로이먼트의 매니페스트에서 지정한 web-deploy를 selector에 지정해주고, type에 ClusterIP를 넣습니다.
외부 포트는 80번으로 지정해줍니다.
서비스를 작성할 때는 연동할 디플로이먼트를 spec.selector.app.kubernetes.io/name을 통해 지정해주어야 합니다.
디플로이먼트와 같이 실행 명령어는 kubectl apply 입니다. 명령어를 통해서 서비스를 실행시켜 보겠습니다.
> kubectl apply -f service-clusterip.yaml
service/web-service created
서비스를 실행시켰으면 , 정상적으로 기동됐는지 확인해보겠습니다.
> kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.233.0.1 <none> 443/TCP 30h
web-service ClusterIP 10.233.34.110 <none> 80/TCP 2s
10.233.34.110의 IP로 cluster ip가 생성된 것을 확인할 수 있습니다.
하지만, cluster ip는 클러스터 내부에서만 통신이 되기 때문에, 제 로컬호스트에서는 접속이 되지 않습니다. 통신이 정상적으로 수행되는 지 확인하기 위해 임시 pod를 하나 생성해서 curl 명령어를 수행해보겠습니다.
> kubectl run test-cluster --image=nginx
kubectl run test-cluster --image=nginx
> kubectl exec -it test-cluster -- /bin/bash
root@test-cluster:/#
root@test-cluster:/# curl "10.233.34.110:80"
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
curl을 통해 명령어를 전송하면, 위와 같이 정상적으로 nginx페이지가 반환되는 것을 확인할 수 있습니다.
계속해서 다른 서비스들을 실습하기 위해 띄워놓은 서비스와 디플로이먼트를 종료하겠습니다.
> kubectl delete pod test-cluser
pod "test-cluster" deleted
> kubectl delete -f service-clusterip.yaml
service "web-service" deleted
> kubectl delete -f nginx-deploy.yaml
deployment.apps "deploy-nginx" deleted
(2) NodePort
NodePort는 각 노드의 특정 포트를 통해 외부 접근을 제공합니다. NodeIp:NodePort를 통해 클러스터 외부에서 클러스터 내부의 pod에 접근할 수 있도록 해줍니다. NAT와 유사한 서비스를 제공합니다.
요청
|
v
+-------------------+
| Node |
| +-------------+ |
| | NodePort | | <- 클러스터의 모든 노드에서 특정 포트가 열림
| +-------------+ |
| | |
| v |
| +-------------+ |
| | Pod | | <- 서비스가 포드로 트래픽을 라우팅
| | (Container)| |
| +-------------+ |
+-------------------+
이제 NodePort를 위한 매니페스트를 작성해보겠습니다.
apiVersion: v1
kind: Service
metadata:
name: web-service-nodeport
spec:
selector:
app.kubernetes.io/name: nginx-deploy
type: NodePort
ports:
- protocol: TCP
nodePort: 31001
port: 80
targetPort: 80
ClusterIp와 마찬가지로 selector에는 생성된 디플로이먼트의 name을 작성해줍니다.
type은 NodePort로 작성하고, NodePort에서는 ports 요소에 nodePort라는 아이템을 작성해줍니다. nodePort는 외부에서 접근하는 포트를 의미합니다. 외부에서 31001이라는 포트로 접근하면 서비스의 80포트로 접속되고, 서비스의 80포트가 pod의 타겟 포트인 80 포트로 다시 전달한다는 의미입니다.
다시 디플로이먼트를 실행시키고 이번에 생성한 NodePort의 매니페스트를 실행시켜 접속이 가능한 지 확인해보겠습니다.
> kubectl apply -f nginx-deploy.yaml
deployment.apps/deploy-nginx created
> kubectl apply -f service-nodeport.yaml
service/web-service-nodeport created
> kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.233.0.1 <none> 443/TCP 31h
web-service-nodeport NodePort 10.233.42.69 <none> 80:31001/TCP 35s
서비스를 확인하면 80:31001과 같이 외부로 노출된 포트가 뒤쪽에 나오는 것을 확인할 수 있습니다.
제 클러스터의 pod를 확인해보면
> kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
deploy-nginx-5b6549999b-7mqbs 1/1 Running 0 106s 10.233.70.51 ubun20-03 <none> <none>
deploy-nginx-5b6549999b-gqgcv 1/1 Running 0 106s 10.233.70.48 ubun20-03 <none> <none>
deploy-nginx-5b6549999b-th7bk 1/1 Running 0 106s 10.233.104.234 ubun20-01 <none> <none>
3번 노드에 두개의 pod가 실행되고 있음을 확인할 수 있고, 3번 노드의 IP에 31001 포트로 접근해보면
이렇게 nginx로 접속이 되는 것을 확인할 수 있습니다.
(3) LoadBalancer
다음으로는 로드밸런서에 대해 알아보겠습니다.
로드밸런서는 서비스에서 NodePort의 상위호환이라고 생각하면 됩니다. 외부 IP와 포트를 지정해놓으면 이 IP와 포트를 따라 NodePort로 들어오고 NodePort에서 Pod로 분배되는 순으로 접속하게 됩니다.
다시 매니페스트를 통해서 로드밸런서가 지정된 서비스를 실행시켜보겠습니다.
apiVersion: v1
kind: Service
metadata:
name: web-service-loadbalancer
spec:
selector:
app.kubernetes.io/name: nginx-deploy
type: LoadBalancer
ports:
- protocol: TCP
port: 80
targetPort: 80
externalIPs:
- 192.168.64.30
매니페스트를 통해 서비스를 생성했습니다. port는 서비스가 사용할 포트, targetPort는 pod가 사용할 포트입니다. 서비스의 80포트로 접속하면 pod의 80번 포트를 통해 디플로이먼트에 접속이 되는 구조입니다.
실행된 서비스의 정보를 확인해보겠습니다.
> kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.233.0.1 <none> 443/TCP 3d5h
web-service-loadbalancer LoadBalancer 10.233.44.171 192.168.64.30 80:30825/TCP 81s
서비스가 위에서 설정한 192.168.64.30이라는 IP와 80라는 외부 포트를 통해 네트워크가 오픈된 것을 볼 수 있습니다. 80포트로 접속하면 nodeport로 설정된 30825 포트로 접속되는 구조입니다. 접속해보기 전에, 새로 생긴 EXTERNAL-IP는 제 로컬호스트에서 통신이 되고 있지 않은 상황이니, 포트포워딩을 따로 설정해주겠습니다. 8080 포트를 30번 서버의 80포트에 매핑시켜주겠습니다.
> kubectl port-forward svc/web-service-loadbalancer 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
8080포트로 접속하면 nginx까지 접속이 되는 것을 확인할 수 있습니다. 이렇게 LoadBalance를 사용하면 하나의 IP를 통해 쿠버네티스 클러스터의 pod에 접속할 수 있습니다.
4. 스토리지 볼륨
스토리지는 도커의 볼륨과 비슷한 역할을 합니다. 쿠버네티스 클러스터의 컨테이너 파일을 보존하기 위해 스토리지 볼륨을 사용합니다.
쿠버네티스의 pod가 삭제된 후 다시 실행하게 되면 기존 컨테이너 내부에 존재하는 파일은 모두 사라집니다. 이러한 문제점을 해결하기 위해 스토리지 볼륨을 사용하는데, 스토리지는 pod처럼 노드 내부에 디스크 공간을 공유하거나 외부 스토리지 시스템과 연결하는 방식이 있습니다.
스토리지에는 emptyDir, hostPath, PersistentVolume의 세가지 종류가 있습니다.
(1) emptyDir
emptyDir는 pod 내부에서 임시적으로 사용하는 볼륨입니다. 따라서 노드의 디스크를 일시적으로 사용합니다, emptyDir 볼륨은 파드가 노드에 할당될 때 처음으로 생성되며, 노드가 실행하는 동안 존재합니다.
emptyDir는 pod에 할당되어 있는 스토리지 성격을 가지고 있어 pod 내부의 서로 다른 컨테이너에서는 데이터 공유가 가능하지만 다른 pod에서는 접근할 수 없습니다.
emptyDir를 생성하기 위한 매니페스트를 작성해보겠습니다.
apiVersion: v1
kind: Pod
metadata:
name: nginx-volume-1
spec:
containers:
- name: nginx-deploy
image: nginx:latest
volumeMounts:
- mountPath: /mount1
name: emptydir-1
volumes:
- name: emptydir-1
emptyDir: {}
container를 생성할 때 volumeMounts를 이용해서 마운트 정보를 설정합니다. volumeMounts에 설정하는 name은 밑에 작성할 volumes와 같은 이름으로 지정해주어야합니다. volumes 내의 emptyDir은 볼륨의 타입을 의미합니다. {}는 추가 옵션을 사용하지 않겠다는 의미입니다.
pod를 실행시키겠습니다.
> kubectl apply -f volume-emptydir.yaml
pod/nginx-volume-1 created
> kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
deploy-nginx-5b6549999b-7mqbs 1/1 Terminating 3 (6d23h ago) 8d 10.233.70.59 ubun20-03 <none> <none>
deploy-nginx-5b6549999b-gqgcv 1/1 Terminating 3 (6d23h ago) 8d 10.233.70.60 ubun20-03 <none> <none>
nginx-volume-1 1/1 Running 0 46s 10.233.104.193 ubun20-01 <none> <none>
pod를 실행시킨 후 확인을 해보면, volume이 생긴 것을 볼 수 있습니다.
이제 exec 명령어를 통해 pod 내부로 들어가 /mount1 디렉터리가 있는지 확인해보겠습니다.
> kubectl exec -it nginx-volume-1 -- /bin/bash
root@nginx-volume-1:/# ls
bin boot dev docker-entrypoint.d docker-entrypoint.sh etc home lib media mnt mount1 opt proc root run sbin srv sys tmp usr var
root@nginx-volume-1:/# cd /mount1
root@nginx-volume-1:/mount1# touch 1.txt
root@nginx-volume-1:/mount1# ls
1.txt
매니페스트 파일에서 지정한 경로가 생성된 것을 확인할 수 있으며, 디렉터리 내부에 들어가 파일까지 생성이 되는 것을 확인할 수 있습니다. emptyDir는 pod 내부에서 해당 볼륨을 바라보고 deploy에서 해당 볼륨을 마운트시키는 방식으로 사용합니다.
(2) hostPath
hostPath는 호스트 노드의 파일 시스템에 저장소를 마운트하는 방식입니다. 호스트 노드에 파일 시스템이 생기기 때문에 서로 다른 pod라고 할 지라도 같은 노드에서 실행되면 같은 파일 시스템을 바라보게 됩니다.
서로 다른 Pod사이에 데이터를 공유할 수 있다는 장점이 있지만, 반대로 해당 노드에 장애가 생기면 다른 pod들에게도 데이터 유실의 단점도 존재합니다.
하지만 보안 위협이 있어 사용이 권고되고 있지는 않습니다.
https://kubernetes.io/ko/docs/concepts/storage/volumes/#hostpath
HostPath 볼륨에는 많은 보안 위험이 있으며, 가능하면 HostPath를 사용하지 않는 것이 좋다. HostPath 볼륨을 사용해야 하는 경우, 필요한 파일 또는 디렉터리로만 범위를 지정하고 ReadOnly로 마운트해야 한다.AdmissionPolicy를 사용하여 특정 디렉터리로의 HostPath 액세스를 제한하는 경우, readOnly 마운트를 사용하는 정책이 유효하려면 volumeMounts 가 반드시 지정되어야 한다.
(3) PV(Persistent Volume)
PV는 외부 스토리지를 의미합니다. PV는 PVC(Persistent Volume Claim)을 이용해서 사용하는데, PVC는 스토리지를 동적으로 바인딩하기 위한 객체를 의미합니다. 쿠버네티스 사용자는 PVC를 통해 외부 스토리지인 PV를 요청하는 식으로 볼륨을 사용합니다.
PV에는 nfs라는 스토리지를 사용하는데, nfs는 network file system의 약자로 네트워크로 연결되는 스토리지 역할을 합니다.
저는 클러스터의 3번 노드에 nfs 서버를 설치하고 나머지 2대의 노드에는 클라이언트를 설치하겠습니다.
1) 1번 노드
> ssh ubun20-01
Last login: Wed Jul 3 12:09:24 2024
(base) choi@ubun20-01:~$ sudo apt install nfs-common
2) 2번 노드
(base) choi@ubun20-01:~$ ssh ubun20-02
Last login: Sat Jun 22 12:06:48 2024
choi@ubun20-02:~$ sudo apt install nfs-common
3) 3번 노드
choi@ubun20-02:~$ ssh ubun20-03
Last login: Sat Jun 22 12:06:41 2024
choi@ubun20-03:~$ sudo apt install nfs-common
choi@ubun20-03:~$ sudo apt install nfs-kernel-server
choi@ubun20-03:~$ systemctl status nfs-server
● nfs-server.service - NFS server and services
Loaded: loaded (/lib/systemd/system/nfs-server.service; enabled; vendor preset: enabled)
Active: active (exited) since Wed 2024-07-03 12:33:38 UTC; 19s ago
Main PID: 4439 (code=exited, status=0/SUCCESS)
Tasks: 0 (limit: 9379)
Memory: 0B
CGroup: /system.slice/nfs-server.service
Jul 03 12:33:36 ubun20-03 systemd[1]: Starting NFS server and services...
Jul 03 12:33:38 ubun20-03 systemd[1]: Finished NFS server and services.
클라이언트와 서버 설치를 모두 마쳤습니다.
이제는 공용 볼륨으로 사용할 디렉터리를 nfs server인 3번 노드에 생성하겠습니다.
choi@ubun20-03:~$ sudo -i
root@ubun20-03:~# cd /tmp/
root@ubun20-03:/tmp# mkdir k8s-pv
root계정으로 전환해서 /tmp 아래에 k8s-pv라는 디렉터리를 만들었습니다. 이 디렉터리를 제 쿠버네티스 클러스터의 PV로 사용하고자 합니다. 설정 파일을 통해 공유 디렉터리를 지정해줍니다.
root@ubun20-03:/tmp# vi /etc/exports
/tmp/k8s-pv 192.168.64.12(rw,no_root_squash)
root@ubun20-03:/tmp# systemctl restart nfs-server
root@ubun20-03:/tmp# systemctl status nfs-server
● nfs-server.service - NFS server and services
Loaded: loaded (/lib/systemd/system/nfs-server.service; enabled; vendor preset: enabled)
Active: active (exited) since Wed 2024-07-03 12:40:26 UTC; 29s ago
Process: 9393 ExecStartPre=/usr/sbin/exportfs -r (code=exited, status=0/SUCCESS)
Process: 9394 ExecStart=/usr/sbin/rpc.nfsd $RPCNFSDARGS (code=exited, status=0/SUCCESS)
Main PID: 9394 (code=exited, status=0/SUCCESS)
Jul 03 12:40:25 ubun20-03 systemd[1]: Starting NFS server and services...
Jul 03 12:40:25 ubun20-03 exportfs[9393]: exportfs: /etc/exports [2]: Neither 'subtree_check' or 'no_subtree_check' specified for export "192.168.64.12:/tmp/k8s-pv".
Jul 03 12:40:25 ubun20-03 exportfs[9393]: Assuming default behaviour ('no_subtree_check').
Jul 03 12:40:25 ubun20-03 exportfs[9393]: NOTE: this default has changed since nfs-utils version 1.0.x
Jul 03 12:40:26 ubun20-03 systemd[1]: Finished NFS server and services.
/etc/exports를 vi로 열어서 공유할디렉터리:허용할IP의 형식으로 한 줄을 추가해줍니다.
이 때, 괄호 안의 rw는 읽기와 쓰기, no_root_squash는 클라이언트의 루트 권한을 인정해준다는 의미입니다.
만약 no_root_squash 옵션이 없다면 클라이언트가 해당 디렉터리에 파일을 쓸 수 있는 권한이 부여되지 않습니다.
파일을 저장한 뒤 nfs server를 재시작해줍니다.
이제 PV 사용을 위한 매니페스트를 작성해보겠습니다.
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-1
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 100Mi
persistentVolumeReclaimPolicy: Retain
storageClassName: pv-test-1
nfs:
server: 192.168.64.13
path: /tmp/k8s-pv
종류는 PersistentVolume, spec의 accessModes는 ReadWriteOnce로 지정합니다.
스토리지 용량은 100M, persistentVolumeReclaimPolicy를 Retain으로 설정하는 데, 여기서 Retain은 PVC가 삭제되어도 PV 내부의 데이터는 유지해주는 옵션입니다.
reclaim에는 retain외에도 Delete, Recycle이 있습니다. Delete는 PVC가 삭제되면 연계되어 있는 외부 스토리지도 삭제해주는 옵션입니다.
다음으로 PVC의 매니페스트 파일을 작성하겠습니다.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-1
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 30Mi
storageClassName: pv-test-1
kind는 PersistentVolumeClaim으로, storageClassName을 앞서 작성한 PV의 className과 같게 작성합니다.
마지막으로 실습을 위한 pod 매니페스트를 작성하겠습니다.
apiVersion: v1
kind: Pod
metadata:
name: nginx-volume
spec:
nodeSelector:
kubernetes.io/hostname: ubun20-02
containers:
- name: nginx-volume
image: nginx:latest
volumeMounts:
- name: nfs-pv-1
mountPath: /mount01
volumes:
- name: nfs-pv-1
persistentVolumeClaim:
claimName: pvc-1
여기서 주의할 점은 nfs server의 설정 파일에서 지정한 2번 노드에서만 접근이 되고 있기 때문에 nodeSelector를 통해 생성될 노드를 2번 노드로 지정해주어야합니다. 그리고 volumeMounts의 name과 아래 volume 설정의 name을 같게 해주어야하며, volume 설정에서의 persistentVolumeClaim의 claimName은 앞서 pvc에서 설정한 이름과 같도록 맞추어주어야 합니다.
이제 볼륨을 실행해 보겠습니다.
> kubectl apply -f volume-PV.yaml
persistentvolume/pv-1 created
PV를 확인하는 명령어는 kubectl get pv 입니다. 생성된 pv의 상태를 확인해보겠습니다.
> kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-1 100Mi RWO Retain Available pv-test-1 33s
매니페스트에서 지정한 내용이 그대로 표시되는 것을 확인할 수 있습니다.
계속해서 PVC도 띄우겠습니다.
> kubectl apply -f volume-PVC.yaml
persistentvolumeclaim/pvc-1 created
PVC도 PV와 비슷하게 kubectl get pvc 명령어로 상태를 확인할 수 있습니다.
> kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-1 Bound pv-1 100Mi RWO pv-test-1 61s
pv-1과 연결되어 있는 것을 확인할 수 있습니다. pvc를 생성했으니 다시 pv를 확인해보겠습니다.
> kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-1 100Mi RWO Retain Bound default/pvc-1 pv-test-1 4m4s
처음 확인했을 때 STATUS는 Available이었는데, 다시 확인해보니 Bound 상태로 변경된 것을 볼 수 있습니다. CLAIM도 기존에는 공백이었지만 pvc-1로 지정된 것을 볼 수 있습니다. 연결이 잘 되었다는 의미입니다.
마지막으로 pod를 생성해보겠습니다.
> kubectl apply -f volume-pod.yaml
pod/nginx-volume created
> kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-volume 1/1 Running 0 16s 10.233.109.184 ubun20-02 <none> <none>
pod가 2번 노드에 정상적으로 생성된 것을 확인할 수 있습니다. 이제 pod의 내부에 들어가서 마운트된 볼륨의 경로에 파일을 생성해보겠습니다.
> kubectl exec -it nginx-volume -- /bin/bash
root@nginx-volume:/# ls
bin boot dev docker-entrypoint.d docker-entrypoint.sh etc home lib media mnt mount01 opt proc root run sbin srv sys tmp usr var
root@nginx-volume:/# cd /mount01
root@nginx-volume:/mount01# ls
root@nginx-volume:/mount01# echo "hello volume" > volume-test.txt
root@nginx-volume:/mount01# ls
volume-test.txt
root@nginx-volume:/mount01# cat volume-test.txt
hello volume
root@nginx-volume:/mount01#
텍스트 파일을 하나 생성했습니다. 이제 공용 볼륨으로 이동해서 파일이 정상적으로 생성됐는지 확인해보겠습니다.
위에서 생성한 3번 노드의 k8s-pv 디렉터리로 이동하겠습니다.
> ssh ubun20-03
Last login: Wed Jul 3 13:17:35 2024 from 192.168.64.1
choi@ubun20-03:~$ cd /tmp/k8s-pv
choi@ubun20-03:/tmp/k8s-pv$ ls
volume-test.txt
choi@ubun20-03:/tmp/k8s-pv$ cat volume-test.txt
hello volume
정상적으로 생성된 것을 확인할 수 있습니다. 도커의 volume과 유사한 동작을 수행합니다.
모든 테스트가 끝났으니 생성의 역순으로 PV 및 pod를 제거합니다. 실습 후에 바로 제거하지 않으면 나중에 확인했을 때 어느 파일을 통해 서비스를 생성했는 지 체크가 바로 되지 않기 때문에 바로 제거하는 습관을 가져야합니다.
> k delete -f volume-pod.yaml
pod "nginx-volume" deleted
> k delete -f volume-PVC.yaml
persistentvolumeclaim "pvc-1" deleted
> k delete -f volume-PV.yaml
persistentvolume "pv-1" deleted
이렇게 간단한 쿠버네티스의 기능들을 알아보는 시간을 가졌습니다.
다음 포스팅에서는 스테이트풀셋, 인그레스, 헬름, job에 대해 알아보겠습니다.
'프로그래밍' 카테고리의 다른 글
[sts] Maven 업데이트 시 무한 로딩 (0) | 2024.07.18 |
---|---|
[kubernetes] 스테이트풀셋(Statefulset) (0) | 2024.07.10 |
[kubernetes] ubuntu 20.04에 python3.11 버전 설치 및 kubespray로 쿠버네티스 설치하기 (2) | 2024.06.11 |
[kubernetes] 쿠버네티스 환경 구축 (0) | 2024.06.11 |
[docker] 도커를 통한 flask 실행 (0) | 2024.05.31 |