ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 06. 쿠버네티스 핵심 개념 (1)
    개발 스터디/데브옵스(DevOps)를 위한 쿠버네티스 마스터 2021. 7. 9. 20:41
    반응형

    이 문서는 인프런 강의 "데브옵스를 위한 쿠버네티스 마스터"을 듣고 작성되었습니다. 최대한 요약해서 강의 내용을 최소로 하는데 목표를 두고 있어서, 더 친절하고 정확한 내용을 원하신다면 강의를 구매하시는 것을 추천드립니다. => 강의 링크

    큐브 시스템 컴포넌트란

    큐브 시스템 컴포넌트란 쿠버네티스(kubenetes) 시스템을 구성하는 중요 컴포넌트입니다. 그림으로 나타내면 다음과 같다.

    마스터 노드(Control Plane Node)는 다음과 같이 구성되어 있다.

    • etcd : 모든 클러스터 데이터를 담는 쿠버네티스의 저장소이다.
    • kube-apiserver : Kubernetes API를 노출한다. 모든 컴포넌트는 이 컴포넌트를 통해서 통신한다.
    • kube-scheduler : 생성된 Pod를 어떤 Node에 배치하고 실핼할 것인지를 선택한다.
    • kube-controller-manager : 실제 리소스를 관리하는 컴포넌트 다음과 같이 크게 4가지로 구성된다.
      1. 노드 컨트롤러 : 노드 상태 관리
      2. 레플리케이션 컨트롤러 : Pod 개수를 조절
      3. 엔드포인트 컨트롤러 : Service와 Pod을 연결
      4. 서비스 어카운트 & 토큰 컨트롤러 : 인증/인가를 담당

    슬레이브 노드(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

     

    위의 명령어 두 줄을 보면, kubeletkube-proxy가 실행되고 있음을 확인할 수 있다.

    Etcd란?

    EtcdKubernetes 클러스터의 저장소로 사용되며, 원래는 키-값 쌍으로 데이터를 저장하는 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가 원칙이다. 또한 PodNAT로 구성된 것 처럼 외부와 격리되어 있다. 외부와 연결하려면 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를 간단하게 작성해보자. 다음을 작성한다.

     

    src/ch06/http-liveness.yaml

    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
Designed by Tistory.