쿠버네티스 데이터베이스 오퍼레이터 스터티 1주차 내용

쿠버네티스 특징

쿠버테니스트 아키텍처

쿠버네티스 아키텍처는 다음과 같다.

Control Plane: k8s 내에서 api, 메타 정보 저장, 배치 스케줄링, 이벤트 처리, 클라우드의 경우 클라우드 플랫폼과의 제어를 관장하는 처리를 하는 마스터 노드에 해당되는 핵심 컴포넌트이다.

Woker Node: application이 동작되는 곳이다. kubectl, kube-proxy, Container Runtime이 존재한다.

쿠버네티스 설치 방식

k8s는 클라우드와 온프렘에서 운영할 수 있다. 클라우드에서는 주로 콘솔이나 제공하는 툴을 사용하여 배포가능하고 배포가 주기적으로 이루어진다면 IaC 를 활용할 수 있는 테라폼을 사용할 수 있다. 온프렘에서는 kubespray 를 이용하여 k8s 클러스터를 구축할 수 있다.

aws에서는 주로 테라폼, eksctl, cloudformation을 많이 사용한다.

실습 시에는 주로 eksctl 로 사용하면 설치시 여러가지 설정을 어느정도 자동화해줌으로 초반에는 eksctl로 설치하는것이 설치가 쉽다

설치는 다음 링크를 참조 https://main.dfdgsw33yvsy6.amplifyapp.com/ko/

핵심 개념

k8s는 서버에서 남는 자원을 효율적으로 관리하며 독립된 환경을 통해 애플리케이션마다 동일 서버내에서 다른 환경을 적용하여 애플리케이션을 배포할 수 있다. 또한 여러가지 리소스를 설정으로 관리가되어 엄청나게 많은 애플리케이션이 있다면 유지보수가 유용하다. 쿠버네티스에서 주로 사용되는 개념은 다음과 같다.

Desired State : 특정 리소스를 지속적으로 바라보면 서 사용자가 생각하는 최종 애플리케이션 배포 상태 를 유지한다.

Namespace : 클러스터를 논리적으로 분리하는 개념, 네임스페이스마다 서로 다른 권한을 설정 할수 있으며 네트워크 정책등을 설정할 수 있다.

쿠버네티스 리소스, 오브젝트 : k8s 리소스에서는 컨테이너의 집합(Pods), 컨테이너의 집합을 관리하는 컨트롤러(Replica Set), 애플리케이션의 인스턴스를 관리하고 업데이트 리소스(Deployment) 등 다양한 리소스가 존재하고 오브젝트로 표현이 가능하다. k8s에서 기본이 되는 리소스는 Pod이고 최소 실행단위이다.

선언형 커맨드 : 사용자가 직접 시스템의 상태를 바꾸지 않고 사용자가 바라는 상태를 선언적으로 기술하여 명령을 내리는 방법이다. 주로 yaml과 같이 사전 정의된 파일에 상태를 정의하면 사용자가 일일이 상태 명령을 내리지 않아도 정의된 내용을 선언된 상태로 유지해준다. 이와 반대되는 개념은 명령형 커맨드이며 명령형은 사용자가 여러 상태를 커맨드라인으로 입력하여 상태를 변경한다.

Amazon EKS

Amazon Elastic Kubernetes Service는 자체 Kubernetes 컨트롤 플레인 또는 노드를 설치, 운영 및 유지 관리할 필요 없이 Kubernetes 실행에 사용할 수 있는 관리형 서비스 이다.

k8s에서는 유지보수가 어려운 Control Plane을 운용해주며 사용자는 Worker Node만 관리하면된다. 또한 aws 서비스와 통합이 용이하기에 초기 서비스 구성 aws 서비스와 연계하여 빠르게 서비스를 구축할 수 있다. 그리고 k8s 버전을 지원하기에 유지가 편리하다.

쿠버네티스 멱등성

쿠버네티스에서 상태를 유지하는지에 대해서 실습을 해본다.

먼저 파드를 모니터링 하기 위한 터미널 창을 연다

watch -d 'kubectl get pod'

그 다음 pod 3개를 배포해본다.

# Deployment 배포(Pod 3개)
kubectl create deployment my-webs --image=nginx --replicas=3
kubectl get pod -w
파드가 3개가 정상적으로 배포된것을 볼 수 있다. 옆의 그림에서도 3개의 파드를 확인할 수 있다.  

파드를 증가 시켜본다.

kubectl scale deployment my-webs --replicas=6
파드 6개가 각각의 노드에 2개식 배포된것을 옆의 그림으로 확인할 수 있다.  

파드를 감소시켜본다.

kubectl scale deployment my-webs --replicas=2
파드는 2개로 줄었고 2,3번 노드에만 남아 있다.  

파드를 강제로 삭제시켜본다. deloyment로 배포했기에 파드는 삭제되어도 다시 생성되어야 한다.

kubectl delete pod --all
파드를 삭제하지만 다시 재생성하며 이번에는 1,3번 노드에 파드가 2개가 생성되어 멱등성이 유지됨을 볼 수 있다.  

쿠버네티스 스토리지

파드는 삭제 시 내부 데이터가 모두 삭제되는 Stateless 애플리케이션이다. 하지만 서비스 운영 시 파드가 삭제되어도 데이터를 보존할 수 있는 Stateful 애플리케이션이 필요하다. 각각의 상태에서 사용되는 스토리지 개념은 다음과 같다.

Stateless : Temporary filesystem, Volume, 아래의 그림에서 emtyDir

Stateful : PV(Persistent Volume) & PVC, 아래의 그림에서 hostPath(잘 사용하지 않음), PVC/PV

볼륨의 형태는 ceph, nfs, aws ebs등 다양한 형태가 사용가능하고 k8s 자체 제공은 hostpath, local을 사용 할 수 도 있다.

CSI (Contaier Storage Interface) 소개

Kubernetes source code 내부에 존재하는 AWS EBS provisioner는 당연히 Kubernetes release lifecycle을 따라서 배포되므로, provisioner 신규 기능을 사용하기 위해서는 Kubernetes version을 업그레이드해야 하는 제약 사항이 있습니다. 따라서, Kubernetes 개발자는 Kubernetes 내부에 내장된 provisioner (in-tree)를 모두 삭제하고, 별도의 controller Pod을 통해 동적 provisioning을 사용할 수 있도록 만들었습니다. 이것이 바로 CSI (Container Storage Interface) driver 이다.

CSI 를 사용하면, K8S 의 공통화된 CSI 인터페이스를 통해 다양한 프로바이더를 사용할 수 있다.

아래의 그림은 CSI driver의 구조입니다. AWS EBS CSI driver 역시 아래와 같은 구조를 가지는데, 오른쪽 StatefulSet 또는 Deployment로 배포된 controller Pod이 AWS API를 사용하여 실제 EBS volume을 생성하는 역할을 한다. 왼쪽 DaemonSet으로 배포된 node Pod은 AWS API를 사용하여 Kubernetes node (EC2 instance)에 EBS volume을 attach 해준다.

Empty Dir 실습

demonset 애플리케이션을 배포하여 파드 삭제 시 데이터가 삭제되어 데이터가 유지되지 않음을 확인하는 실습

실습 애플이케션은 date 명령어로 현재 시간을 10초 간격으로 /home/pod-out.txt 파일에 저장한다 만약 삭제 시 이전 데이터를 볼수 없어야한다.

# 파드 배포
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/date-busybox-pod.yaml
cat date-busybox-pod.yaml | yh
kubectl apply -f date-busybox-pod.yaml

배포 후 파일을 확인해본다. Sat Oct 21 12:14:26 UTC 2023 으로 데이터가 시작된다.

kubectl exec busybox -- tail -f /home/pod-out.txt
Sat Oct 21 12:14:26 UTC 2023
Sat Oct 21 12:14:36 UTC 2023
Sat Oct 21 12:14:46 UTC 2023
Sat Oct 21 12:14:56 UTC 2023

삭제 후 다시 확인해본다. Sat Oct 21 12:15:26 UTC 2023 로 데이터가 시작되어 위의 데이터가 삭제된것을 확인 할수 있다.

kubectl delete pod busybox
kubectl apply -f date-busybox-pod.yaml
kubectl exec busybox -- tail -f /home/pod-out.txt
Sat Oct 21 12:15:26 UTC 2023
Sat Oct 21 12:15:36 UTC 2023

AWS EBS로 PVC/PV 파드 실습

eks는 aws에서 동작하므로 aws에서 주로사용하는 스토리지인 ebs로 동작을 실습해본다.

ebs-csi-controller : EBS CSI driver 동작 : 볼륨 생성 및 파드에 볼륨 연결

PVC를 생성한다.(gp3 의 경우 storageClassName not found 에러가 발생하여 gp2로 생성, k8s 경우 버전마다 yaml 형식이 다르므로 해당 내용은 k8s 버전을 확인하여 yaml 내용확인필요)

cat <<EOT > awsebs-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ebs-claim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 4Gi
  storageClassName: gp2
EOT
kubectl apply -f awsebs-pvc.yaml
kubectl get pvc,pv

파드를 생성한다.

cat <<EOT > awsebs-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  terminationGracePeriodSeconds: 3
  containers:
  - name: app
    image: centos
    command: ["/bin/sh"]
    args: ["-c", "while true; do echo **\**$(date -u) >> /data/out.txt; sleep 5; done"]
    volumeMounts:
    - name: persistent-storage
      mountPath: /data
  volumes:
  - name: persistent-storage
    persistentVolumeClaim**:
      claimName: ebs-claim
EOT
kubectl apply -f awsebs-pod.yaml

리소스 생성확인

kubectl get pvc,pv,pod
NAME                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/ebs-claim   Bound    pvc-13b8a6d1-c1f2-47ae-8502-349ccf42a85e   4Gi        RWO            gp2            102s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
persistentvolume/pvc-13b8a6d1-c1f2-47ae-8502-349ccf42a85e   4Gi        RWO            Delete           Bound    default/ebs-claim   gp2                     69s

NAME                               READY   STATUS    RESTARTS   AGE
pod/app                            1/1     Running   0          72s

저장내용확인

kubectl exec app -- tail -f /data/out.txt
Sat Oct 21 13:12:45 UTC 2023
Sat Oct 21 13:12:50 UTC 2023
Sat Oct 21 13:12:55 UTC 2023
Sat Oct 21 13:13:00 UTC 2023
Sat Oct 21 13:13:05 UTC 2023
Sat Oct 21 13:13:10 UTC 2023
Sat Oct 21 13:13:15 UTC 2023
Sat Oct 21 13:13:20 UTC 2023
Sat Oct 21 13:13:25 UTC 2023
Sat Oct 21 13:13:30 UTC 2023
Sat Oct 21 13:13:35 UTC 2023
Sat Oct 21 13:13:40 UTC 2023

파드 삭제 후 기존 pvc 부착(내용이 유지되는 것을 볼수있다)

k delete pod app
kubectl apply -f awsebs-pod.yaml
kubectl exec app -- tail -f /data/out.txt
Sat Oct 21 13:13:25 UTC 2023
Sat Oct 21 13:13:30 UTC 2023
Sat Oct 21 13:13:35 UTC 2023
Sat Oct 21 13:13:40 UTC 2023
Sat Oct 21 13:13:45 UTC 2023
Sat Oct 21 13:13:50 UTC 2023
Sat Oct 21 13:13:55 UTC 2023
Sat Oct 21 13:14:00 UTC 2023
Sat Oct 21 13:14:05 UTC 2023

cloud이기에 pvc의 볼륨을 늘릴수있다. 하지만 줄일 수는 없다.

# 현재 pv 의 이름을 기준하여 4G > 10G 로 증가 : .spec.resources.requests.storage의 4Gi 를 10Gi로 변경
kubectl get pvc ebs-claim -o jsonpath={.spec.resources.requests.storage} ; echo
kubectl get pvc ebs-claim -o jsonpath={.status.capacity.storage} ; echo
kubectl patch pvc ebs-claim -p '{"spec":{"resources":{"requests":{"storage":"10Gi"}}}}'

쿠버네티스 네트워크

k8s에서 네트워크 환경을 구성해주는 것을 k8s cni 라고 한다. aws에서는 파드의 ip를 할당하기 위해 aws vpc cni라는 것이 있으며 파드의 ip와 네트워크 대역과 워커노드의 ip 대역이 같아서 파드간 직접 통신이 가능하다.

aws에서 k8s 네트워크의 특징은 다음과 같다.

  • vpc와 통합 - vpc flow logs, vpc 라우팅 정책, 보안그룹 사용가능
  • vpc eni에 미리 할당된 ip를 하드에서 사용이 가능하다.

아래의 그림에서 노드와 파드의 아이피 대역이 일치함을 확인할 수 있다.

네트워크 실습

테스트용 파드 생성(netshoot 은 zsh를 제공함)

# 테스트용 파드 netshoot-pod 생성
cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: netshoot-pod
spec:
  replicas: 3
  selector:
    matchLabels:
      app: netshoot-pod
  template:
    metadata:
      labels:
        app: netshoot-pod
    spec:
      containers:
      - name: netshoot-pod
        image: nicolaka/netshoot
        command: ["tail"]
        args: ["-f", "/dev/null"]
      terminationGracePeriodSeconds: 0
EOF

# 파드 이름 변수 지정
PODNAME1=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[0].metadata.name})
PODNAME2=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[1].metadata.name})
PODNAME3=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[2].metadata.name})

# 파드 확인
kubectl get pod -o wide
kubectl get pod -o=custom-columns=NAME:.metadata.name,IP:.status.podIP

파드가 생성되면 워커 노드 eniY@ifN 추가되고 라우팅 테이블에도 정보가 추가된다

테스트용 파드 접속(exec) 후 3개의 파드가 서로 통신이 가능한지 ping 테스트를 해본다.

# 테스트용 파드 접속(exec) 후 Shell 실행
kubectl exec -it $PODNAME1 -- zsh

# 2번 파드 ping 테스트
ping 192.168.2.73
PING 192.168.2.73 (192.168.2.73) 56(84) bytes of data.
64 bytes from 192.168.2.73: icmp_seq=1 ttl=125 time=1.31 ms
64 bytes from 192.168.2.73: icmp_seq=2 ttl=125 time=1.24 ms
64 bytes from 192.168.2.73: icmp_seq=3 ttl=125 time=1.35 ms
^C
--- 192.168.2.73 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 1.244/1.302/1.352/0.044 ms

# 3번 파드 ping 테스트
PING 192.168.1.22 (192.168.1.22) 56(84) bytes of data.
64 bytes from 192.168.1.22: icmp_seq=1 ttl=125 time=1.13 ms
64 bytes from 192.168.1.22: icmp_seq=2 ttl=125 time=1.19 ms
64 bytes from 192.168.1.22: icmp_seq=3 ttl=125 time=1.11 ms
^C
--- 192.168.1.22 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 1.107/1.142/1.187/0.033 ms

파드에서 외부 통신 테스트 확인(파드의 워크노드에 있는 외부아이피를 통해 외부와 통신할 수 있다)

# 작업용 EC2 : pod-1 Shell 에서 외부로 ping
kubectl exec -it $PODNAME1 -- ping -c 1 www.google.com
kubectl exec -it $PODNAME1 -- ping -i 0.1 www.google.com
PING www.google.com (142.250.206.228) 56(84) bytes of data.
64 bytes from kix06s10-in-f4.1e100.net (142.250.206.228): icmp_seq=1 ttl=103 time=16.0 ms
64 bytes from kix06s10-in-f4.1e100.net (142.250.206.228): icmp_seq=2 ttl=103 time=16.0 ms
64 bytes from kix06s10-in-f4.1e100.net (142.250.206.228): icmp_seq=3 ttl=103 time=16.0 ms
64 bytes from kix06s10-in-f4.1e100.net (142.250.206.228): icmp_seq=4 ttl=103 time=16.0 ms
64 bytes from kix06s10-in-f4.1e100.net (142.250.206.228): icmp_seq=5 ttl=103 time=16.2 ms
64 bytes from kix06s10-in-f4.1e100.net (142.250.206.228): icmp_seq=6 ttl=103 time=16.0 ms

쿠버네티스 서비스 실습

쿠버네티스에서 외부에서 파드의 접근하기위해서 서비스가 필요한데 서비스 형태는 아래와 같다.

# 작업용 EC2 - 디플로이먼트 & 서비스 생성
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/2/echo-service-nlb.yaml
cat echo-service-nlb.yaml | yh
**kubectl apply -f echo-service-nlb.yaml**

nlb를 배포하면 aws ec2 > load balancer에서 provisining 되는것을 확인 할수 있다.

서비스를 확인해본다.

kubectl get svc,ep,ingressclassparams,targetgroupbindings
NAME                      TYPE           CLUSTER-IP      EXTERNAL-IP                                                                         PORT(S)        AGE
service/kubernetes        ClusterIP      10.100.0.1      <none>                                                                              443/TCP        113m
service/svc-nlb-ip-type   LoadBalancer   10.100.215.68   k8s-default-svcnlbip-ea7cd25388-3ff618a208573999.elb.ap-northeast-2.amazonaws.com   80:31013/TCP   19s

NAME                        ENDPOINTS                               AGE
endpoints/kubernetes        192.168.1.57:443,192.168.3.241:443      113m
endpoints/svc-nlb-ip-type   192.168.1.117:8080,192.168.2.213:8080   19s

NAME                                   GROUP-NAME   SCHEME   IP-ADDRESS-TYPE   AGE
ingressclassparams.elbv2.k8s.aws/alb                                           64m

NAME                                                               SERVICE-NAME      SERVICE-PORT   TARGET-TYPE   AGE
targetgroupbinding.elbv2.k8s.aws/k8s-default-svcnlbip-af02c27d84   svc-nlb-ip-type   80             ip            15s

k8s-default-svcnlbip-ea7cd25388-3ff618a208573999.elb.ap-northeast-2.amazonaws.com 접속정보로 애플리케이션에 접근이 가능하다.

curl http://k8s-default-svcnlbip-ea7cd25388-3ff618a208573999.elb.ap-northeast-2.amazonaws.com/

Hostname: deploy-echo-79d5d496bf-8k4mq

Pod Information:
	-no pod information available-

Server values:
	server_version=nginx: 1.13.0 - lua: 10008

Request Information:
	client_address=192.168.3.112
	method=GET
	real path=/
	query=
	request_version=1.1
	request_uri=http://k8s-default-svcnlbip-ea7cd25388-3ff618a208573999.elb.ap-northeast-2.amazonaws.com:8080/

Request Headers:
	accept=*/*
	host=k8s-default-svcnlbip-ea7cd25388-3ff618a208573999.elb.ap-northeast-2.amazonaws.com
	user-agent=curl/8.3.0

Request Body:
	-no body in request-

스테이트풀셋 & 헤드리스서비스

스테이트풀셋은 상태를 유지하는 애플리케이션을 관리하기 위한 API 오브젝트입니다. 이는 무상태 애플리케이션을 위한 Deployment와 유사하나, 몇 가지 중요한 차이점이 있습니다.

  • 예측 가능한 고유 이름: 스테이트풀셋에 의해 생성된 각 포드는 고유한, 예측 가능한 이름을 가집니다. 예를 들어, myapp-0, myapp-1 등으로 이름이 부여됩니다.
  • 순차적, 자동 롤링 업데이트: 포드는 순차적으로 업데이트되며, N-1번째 포드가 성공적으로 업데이트되고 실행되기 전까지 N번째 포드는 업데이트되지 않습니다.
  • 고정된 호스트 이름: 각 포드는 고정된 호스트 이름과 네트워크 식별자를 가집니다.
  • 지속적인 스토리지: 각 포드는 자체적인 스토리지 볼륨을 가지며, 포드가 재배치되더라도 이 데이터는 유지됩니다.

헤드리스 서비스는 클러스터의 IP가 할당되지 않은 서비스입니다. 이를 통해 서비스에 연결할 때, 서비스에 연결하는 대신 연결을 요청한 포드의 집합에 직접 연결할 수 있습니다. 스테이트풀셋과 함께 사용될 때, 헤드리스 서비스는 각 포드의 DNS 엔트리를 생성해주어, 포드간에 고정된 호스트네임으로 통신할 수 있게 도와줍니다.

  • 클러스터 IP 없음: 헤드리스 서비스는 클러스터 IP를 가지지 않습니다.
  • DNS 엔트리: 헤드리스 서비스는 연결된 각 포드에 대한 DNS A 레코드를 생성합니다.
  • 직접 포드 연결: 서비스를 통해 연결을 요청하면, 쿠버네티스는 서비스에 연결하는 대신 연결을 요청한 포드 집합 중 하나에 직접 연결합니다.

+ Recent posts