ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 08. 쿠버네티스 핵심 개념 (3)
    개발 스터디/데브옵스(DevOps)를 위한 쿠버네티스 마스터 2021. 7. 25. 19:42
    반응형

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

    네임스페이스

    namespace란 물리 쿠버네티스 클러스터 내에서 동작하는 가상 쿠버네티스 클러스터이다. 약간 실제 물리서버에서 동작하는 VM과 성격이 비슷한데, 쉽게 생각해서 리소스들을 분리된 영역으로 격리시켜 관리하는 기술이다.

     

    기본적으로 쿠버네티스 클러스터에는 다음의 4가지 네임스페이스가 자동으로 생성된다.

    $ kubectl get namespace
    NAME              STATUS   AGE
    default           Active   15d
    kube-node-lease   Active   15d
    kube-public       Active   15d
    kube-system       Active   15d

     

    이제 namespace 하나를 생성해보자. 터미널에 다음과 같이 ns-test.yaml을 만든다.

     

    src/ch08/k8s/ns-test.yaml

    apiVersion: v1
    kind: Namespace
    metadata:
      creationTimestamp: null
      name: ns-test
    spec: {}
    status: {}

     

    이제 터미널에 다음을 입력한다.

    $ kubectl create -f ns-test.yaml
    namespace/ns-test created

     

    namespace가 잘 만들어졌는지 확인해보자.

    $ kubectl get ns
    NAME              STATUS   AGE
    default           Active   15d
    kube-node-lease   Active   15d
    kube-public       Active   15d
    kube-system       Active   15d
    # 추가되었다.
    ns-test           Active   39s

     

    이제 namespace 안에 pod을 하나 만들어보자. 터미널에 다음을 입력한다.

    $ kubectl run nginx --image=nginx -n ns-test
    pod/nginx created

     

    이제 pod이 잘 생성되었는지 확인해보자.

    $ kubectl get pod
    No resources found in default namespace.

     

    분명 리소스를 생성했는데 안보인다. 왜냐하면, 기본적으로 namespace는 "default"로 잡혀있다. 내가 원하는 namespace의 리소스를 확인하고 싶다면 다음과 같이 입력하면 된다.

    # kubectl get <resource> -n <namespace>
    $ kubectl get pod -n ns-test
    NAME    READY   STATUS    RESTARTS   AGE
    nginx   1/1     Running   0          5m8s

     

    namespace 역시 다음과 같이 삭제할 수 있다.

    $ kubectl delete -f ns-test.yaml 
    namespace "ns-test" deleted

    서비스

    ServicePod 집합에서 실행 중인 애플리케이션을 네트워크 서비스로 노출하는 추상화된 방법이다. Pod는 일시적으로 생성되는 컨테이너의 집합인데, 실제 리소스 생성/수정/삭제 시에, IP 주소가 지속적으로 변동되기 떄문에 이를 추적하고 부하를 분산시켜야 한다. 이를 해결하기 위한 것이 바로 Service이다.

     

    서비스는 다음과 같이 구성할 수 있다.

     

    src/ch08/k8s/simple-app-svc-v1.yaml

    # service
    apiVersion: v1
    kind: Service
    metadata:
      name: simple-app-svc
    spec:
      sessionAffinity: ClientIP
      ports:
      - port: 80
        targetPort: 8080
      selector:
        app: simple-app
    
    ---
    # deployment
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: simple-app-dep
      labels:
        app: simple-app
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: simple-app
      template:
        metadata:
          labels:
            app: simple-app
        spec:
          containers:
          - name: simple-app
            image: gurumee92/simple-app:v1
            ports:
            - containerPort: 8080

     

    위의 코드는 Service이고, 아래 --- 아래 코드는 Deployment이다. 즉 위의 코드는 simple-app 컨테이너 3개를 구동시키는 Deployment와, 이를 외부로 노출시키는 Service를 구동시키는 것이다. 다음 명령어로 만들 수 있다.

     

    $ pwd
    /home/gurumee/k8s/ch08
    
    $ kubectl create -f simple-app-svc.yaml
    service/simple-app-svc created
    deployment.apps/simple-app-dep created

     

    다음 명령어로 생성된 Service 목록을 확인할 수 있다.

    $ kubectl get svc
    NAME             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
    kubernetes       ClusterIP   10.96.0.1        <none>        443/TCP   6d11h
    simple-app-svc   ClusterIP   10.105.112.199   <none>        80/TCP    50s

     

    여기서 생성된 Pod들을 보자.

    $ kubectl get pod -o wide
    NAME                              READY   STATUS    RESTARTS   AGE   IP          NODE     NOMINATED NODE   READINESS GATES
    simple-app-dep-5d4c8d678b-hcqzj   1/1     Running   0          21m   10.32.0.3   slave2   <none>           <none>
    simple-app-dep-5d4c8d678b-jljj2   1/1     Running   0          21m   10.32.0.2   slave2   <none>           <none>
    simple-app-dep-5d4c8d678b-p5sdn   1/1     Running   0          21m   10.32.0.4   slave2   <none>           <none>

     

    이 때 IP를 기억해두자. 서비스의 정보를 확인한다.

    $ kubectl describe svc simple-app-svc
    Name:              simple-app-svc
    Namespace:         default
    Labels:            <none>
    Annotations:       <none>
    Selector:          app=simple-app
    # 기본 타입은 ClusterIP이다.
    Type:              ClusterIP
    IP Family Policy:  SingleStack
    IP Families:       IPv4
    IP:                10.105.112.199
    IPs:               10.105.112.199
    Port:              <unset>  80/TCP
    TargetPort:        8080/TCP
    # 엔드포인트에 Pod들의 IP가 할당된 것을 확인할 수 있다.
    Endpoints:         10.32.0.2:8080,10.32.0.3:8080,10.32.0.4:8080
    Session Affinity:  ClientIP
    Events:            <none>

     

    여기서 EndpointsPod들의 IP가 할당된 것을 확인할 수 있다. 또한 아무 타입 지정을 하지 않으면 ClusterIP로 지정된다. 이게 모든 서비스의 원형 타입이라고 생각하면 된다. 현재 이렇게 분산된 경우, 한 가지만 더 알아두자. 현재 설정으로는 서비스 세션이 고정되지 않는다. 이 때 세션을 고정하기 위한 설정이 있는데 바로 sessionAffinity이다. 다음과 같이 수정한다.

     

    src/ch08/k8s/simple-app-svc-v2.yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: simple-app-svc
    spec:
      # 세션을 고정하기 위한 설정
      sessionAffinity: ClientIP
    
      # ...
    # ...

     

    이렇게 하면, 한 번 요청을 한 경우, 이후부터의 요청은 최초 요청한 Pod에게 요청을 지속적으로 발생하게 된다. 또한 외부 IP와 연결이 필요한 경우는 Service와 더불어서 Endpoints라는 리소스가 필요하다. 이런 식으로 외부 IP와 연동할 수 있다.

     

    서비스

    apiVersion: v1
    kind: Service
    metadata:
      name: external-service
    spec:
      ports:
      - port: 80

     

    엔드포인트

    apiVersion: v1
    kind: Endpoints
    metadata: 
      name: external-service
    subnets:
      - addresses:
        - ip: 11.11.11.11
          ip: 22.22.22.22
        ports:
        - port: 80

     

    다음 절부터는 VM이 아닌 GCP 혹은 EKS 등의 Public Cloud가 제공하는 쿠버네티스 클러스터를 이용하자.

    서비스를 expose하는 방법 (1) NodePort

    서비스를 노출하는 3가지 방법 중 하나이다. 노드 자체 포트를 사용하여 Pod로 리다이렉트 한다. 다음과 같이 서비스 파일을 생성한다.

     

    src/k8s/ch08/simple-app-np.yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: simple-app-np
    spec:
      type: NodePort
      selector:
        app: simple-app
      ports:
      - protocol: TCP
        port: 80
        targetPort: 8080
        nodePort: 30001

     

    위에서 typeNodePort로 지정되고, ports에서 nodePort가 "30001"로 지정된 것을 확인할 수 있다. 터미널에서 다음과 같이 입력하면 서비스를 생성할 수 있다.

     

    $ kubectl create -f simple-app-np.yaml
    service/simple-app-np created

     

    또한 서비스 상태를 확인해보자.

    $ kubectl get svc
    NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
    kubernetes      ClusterIP   10.112.0.1      <none>        443/TCP        7m26s
    simple-app-np   NodePort    10.112.13.203   <none>        80:30001/TCP   34s

     

    여기서 80:30001 로 묶여 있는데, Compute Engine 대시보드로 가보면 다음 노드들이 구성된 것을 확인할 수 있다.

    이 때 해당 노드 IP:nodePort로 구동되는 Pod들을 호출할 수 있다. 호출 이전에 방화벽 정책을 통해 NodePort에 대해서 외부로 공개해주어야 한다.

    $ gcloud compute firewall-rules create simple-app-svc-rules --allow=tcp:30001

     

    그 후 노드 IP:노드 포트로 요청을 할 수가 있다.

    # curl <노드 IP>:<노드 포트>
    $ curl 34.64.75.5:30001
    Hello World v1%  
    
    $ curl 34.64.132.245:30001
    Hello World v1%  
    
    $ curl 34.64.143.114:30001
    Hello World v1%  

    서비스를 expose하는 방법 (2) LoadBalancer

    서비스를 노출하는 3가지 방법 중 하나이다. 외부 게이트웨이를 사용해 노드 포트로 리다이렉트 한다.

     

    src/ch08/k8s/simple-app-lb.yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: simple-app-lb
    spec:
      type: LoadBalancer
      selector:
        app: simple-app
      ports:
      - protocol: TCP
        port: 80
        targetPort: 8080

     

    여기서 typeLoadBalancer인 것을 알아두자. 그리고 nodePort를 지정하지 않으면 30000 번대 포트에서 무작위로 선택이 된다. 한 번 만들어두자.

    $ kubectl create -f simple-app-lb.yaml
    service/simple-app-lb created

     

    그 다음 시간이 좀 지나서 확인해보면 다음과 같이 생성됨을 확인할 수 있다.

    $ kubectl get svc
    NAME            TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
    kubernetes      ClusterIP      10.112.0.1      <none>          443/TCP        5m59s
    simple-app-lb   LoadBalancer   10.112.15.122   34.64.122.149   80:31694/TCP   74s

     

    여기에 EXTERNAL-IP에 IP가 할당된 것을 확인할 수 있다. 애는 GCP 네트워크 서비스에서 제공하는 "부하 분산" 즉 LoadBalacner이다. 다음과 같이 확인할 수 있다.

    외부 IP가 동일하다. 이제 이 IP로 호출할 수 있다. 이떄는 nodePort가 아닌 port로 지정된 포트 번호로 호출해야 한다.

    $ curl 34.64.122.149:80
    Hello World v1% 

    서비스를 expose하는 방법 (3) Ingress

    서비스를 노출하는 3가지 방법 중 하나이다. 하나의 IP 주소를 통해 여러 서비스를 제공하는 특별한 메커니즘이다. 인그레스 방법은 설정이 조금 빡세다. 먼저 NodePort용 서비스를 생성해둔다.

    $ kubectl create -f simple-app-np.yaml
    service/simple-app-np created

     

    그 후 다음과 같이 인그레스를 만드는 설정 파일을 생성한다.

     

    src/ch08/k8s/simple-app-ingress.yaml

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: simple-app-ingress
    spec:
      rules:
      - host: gurumee.com
        http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: simple-app-np
                port:
                  number: 80

     

    hostPod들이 구성된 도메인을 연결해준다. 그 후 http.paths.path를 지정해주고 https.paths.backend.service.namePod을 연결해준다. 이제 터미널에 다음을 입력하여 인그레스를 생성한다.

    $ kubectl create -f simple-app-ingress.yaml
    ingress.networking.k8s.io/simple-app-ingress created

     

    다음과 같이 확인할 수 있다.

    $ kubectl get ingress
    NAME                 CLASS    HOSTS         ADDRESS         PORTS   AGE
    simple-app-ingress   <none>   gurumee.com   34.149.182.51   80      94s

     

    이제 이 주소로 curl을 날려보자. (여기서는 kubectl이 실행되는 터미널에서 진행해야 한다.)

    $ curl 34.149.182.51
    response 404 (backend NotFound), service rules for the path non-existent 

     

    gurumee.com이 없기 때문에 404가 뜬다. 이걸 허용하려면 원래 도메인 설정이 필요하나 귀찮으니까 /etc/hosts에서 다음과 같이 수정한다.

    # Kubernetes-managed hosts file.
    127.0.0.1       localhost
    
    # 수정된 곳
    # ingress IP    매핑된 host
    34.149.182.51   gurumee.com
    
    ::1     localhost ip6-localhost ip6-loopback
    fe00::0 ip6-localnet
    fe00::0 ip6-mcastprefix
    fe00::1 ip6-allnodes
    fe00::2 ip6-allrouters
    172.17.0.4      cs-595878838943-default-boost-l8dqx

     

    그 후 이제는 "Host"에 curl을 날려보자.

    $ curl gurumee.com
    Hello World v1

    kubectl이 실행되는 터미널이 아니라면 어차피 외부에 지정된 도메인이 아니므로 이런식으로 호출은 불가능하다.

Designed by Tistory.