Kubernetes를 배워보자 9일차 - Service, NodePort, Ingress-controller, Readiness Probe

0

kubernetes

목록 보기
9/13

Service

Service는 kubernetes 리소스로 pod에 접근하기 위한 IP주소를 제공한다. pod자체에 IP가 존재하는데 불구하고 Service를 만들어 사용하는 이유가 무엇일까??

1. Service 사용

  1. pod는 일시적이다. 언제든 재시작될 수 있으며 재시작되면 이전의 IP주소가 아닌 새로운 IP주소가 배정된다.
  2. pod에 할당되는 새로운 IP주소를 미리 알수가 없다.
  3. pod가 수평 스케일링되는 경우(동일한 pod가 여러개로 배포되는 경우)에 IP주소가 여러개가 있게 된다. 그러나, 사용자 입장에서는 하나의 IP로 접근하여 어떤 pod든지 간에 응답을 받으면 된다.
    이러한 문제를 해결하기 위해서 kubernetes service 리소스가 존재하는 것이다.

즉, Service는 동일한 서비스를 제공하는 파드 그룹에 지속적인 단일 접점을 만들려고 할 때 생성하는 리소스인 것이다. 각 서비스는 절대 변경되지 않는 IP, Port가 있으며 이는 서비스가 다운되지 않는 한 동일하다. 클라이언트는 IPPort를 통해서 Service에 접근할 수 있고, Service는 연결된 pod에 요청을 리다이렉트한다.

서비스 하나에 여러 파드들이 연결될 수 있다는 것은, 서비스에 들어온 요청이 연결된 파드들 중 한개로 전달될 수 있다는 이야기이고 이는 즉 로드밸런싱된다는 말이다. 서비스에 파드를 연결하는 것은 label selector를 사용하면 된다.

이제 Service를 만들어보도록 하자.

  • kubia-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  selector:
    app: kubia
  ports:
  - port: 80
    targetPort: 8080

kubia라는 서비스를 만들고, 해당 서비스의 port80포트를 연다는 것이다. 그리고 80포트로 들어온 요청에 대해서는 app=kubia라는 label을 가진 pod의 8080포트로 포워딩하겠다는 의미이다.

이제 service를 실행해보도록 하자.

kubectl create -f ./kubia-svc.yaml
service/kubia created

kubectl get svc
NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
kubia        ClusterIP   10.96.5.114   <none>        80/TCP    111s

CLUSTER-IP는 Service의 IP로 클러스터 내부에 배정된 IP이다. 이는 클러스터 내부에서만 사용할 수 있다는 점에 유의하자. port는 위에서 언급했듯이 80포트로 열리게 된다.

다음으로 app=kubia에 연결된 pod가 없으므로 pod를 만들어주도록 한다. 이는 이전에 사용했던 replicaSet을 사용한다.

  • kubia-replicaset.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: kubia
spec:
  replicas: 3
  selector:
    matchLabels:
      app: kubia
  template:
    metadata:
      labels:
        app: kubia
    spec:
      containers:
      - name: kubia
        image: luksa/kubia
        ports:
          - name: http
            containerPort: 8080
          - name: https
            containerPort: 8443

이를 실행시키도록 하자.

kubectl create -f ./kubia-replicaset.yaml
replicaset.apps/kubia created

kubectl get po
NAME          READY   STATUS    RESTARTS   AGE
kubia-2mjw4   1/1     Running   0          3m9s
kubia-bbshd   1/1     Running   0          3m9s
kubia-xflcp   1/1     Running   0          3m9s

pod들이 배포되었으므로, service의 cluster-ip에 요청을 보내보도록 하자.

kubectl exec kubia-bbshd -- curl -s http://10.96.5.114
You've hit kubia-xflcp

kubectl exec을 사용하여 pod내부에서 curl을 실행한 것은 cluster-ip가 pod내부에서만 의미있는 ip이기 때문이다. 즉, 클러스터 내부에서만 쓰이는 ip이기 때문이다. 이렇게 요청을 보냈을 때 문제없이 응답이 온 것을 볼 수 있다. 재밌는 것은 kubia-xflcp가 응답으로 왔는데, kubia service에 대한 요청이 kubia-xflcp으로 포워드되었다는 것이다. 이는 service가 하나의 로드밸런싱을 했다고 볼 수 있다.

                                 | <-> kubia-2mjw4
client <---> kubia service <---> | <-> kubia-bbshd
                                 | <-> kubia-xflcp(선택)

클라이언트의 요청은 kubia service를 통해서 연결된 파드에 랜덤하게 보내진다. 만약, 클라이언트의 요청이 응답이 온 파드에서 계속해서 인터렉션이 이루어지길 바란다면 sessionAffinity를 설정하면 된다. sessionAffinityNoneClientIP라는 두 가지 유형의 서비스 세션 어피니티가 존재한다. ClientIP로 설정하게되면 같은 클라이언트 IP에 대해서는 하나의 파드를 선정해, 계속해서 동일한 파드에서 클라이언트로 응답을 전달한다.

apiVersion: v1
kind: Service
spec:
  sessionAffinity: ClientIP
  ...

위와 같이 설정하면 서비스 프록시는 동일한 클라이언트 IP의 모든 요청을 동일한 파드로 전달한다.

하나의 service에서 여러 개의 포트를 노출시킬 수 있는데, 이 경우 port의 이름을 지어주어야 한다. 주로 http, https용으로 나누어 포트를 열어주곤 한다.

먼저 현재 kubia service를 삭제해주도록 하자.

kubectl delete -f kubia-svc.yaml
service "kubia" deleted

다음으로 다중 포트를 가진 kubia service를 만들어주도록 하자.

apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  selector:
    app: kubia
  ports:
  - name: http
    port: 80
    targetPort: 8080
  - name: https
    port: 443
    targetPort: 8443

ports부분에 리스트로 name을 가진 port 두개가 열린 것을 볼 수 있다. 하나는 http이고 하나는 https인 것이다. http는 서비스의 80포트를 열고 pod의 8080 포트에 연결되며, https는 서비스의 443포트를 열고 pod의 8443포트와 연결된다.

service를 생성하고 이를 확인하면 다음과 같다.

kubectl create -f kubia-svc.yaml
service/kubia created

kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
kubia        ClusterIP   10.108.182.101   <none>        80/TCP,443/TCP   4s

80, 443포트가 열린 것을 확인할 수 있다. 재미난 것은 두 개 모두 TCP로 써있는데, 사실 service는 http로 통신하는 것이 아니라, TCP, UDP패킷을 보내는 통신을 한다. 때문에 http의 한 요소인 쿠키 어피니티가 없는 것이다.

포트의 이름을 지정하면 service의 실제 포트가 몇 번으로 열려있는 지와 상관없이 포트 이름으로만으로도 통신이 가능하다. 이는 실제 service와 통신하는데 있어서 클라이언트가 cluster-ip가 아닌 다른 방법으로 접근한다는 것을 의미한다. 왜냐하면 cluster-ip와 port는 어떤 값이 있을 지 클라이언트 입장에서 모르기 때문이다.

이를 위해서 kubernetes는 service가 만들어지고 난 뒤에 클라이언트 pod가 만들어지면, 클라이언트 pod에 대해서 service의 환경변수를 자동으로 설정해준다. 한 가지 유념해야할 것은 service가 먼저 만들어진 후에 클라이언트 pod를 생성해야 한다는 것이다. 테스트를 위해서 pod kubia-2mjw4를 삭제했다가, 다시 기동되면 환경변수 설정이 어떻게 되었는 지 확인해보기로 하자.

kubectl delete pod kubia-2mjw4
pod "kubia-2mjw4" deleted

이제 kubectl exec명령어를 이용해서 service에 대한 환경변수가 설정되었는 지 확인하도록 하자.

 kubectl exec kubia-hw69s env
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
...
KUBIA_SERVICE_PORT_HTTP=80
KUBIA_SERVICE_PORT_HTTPS=443
KUBIA_PORT=tcp://10.108.182.101:80
KUBIA_PORT_80_TCP_PROTO=tcp
KUBIA_PORT_80_TCP_ADDR=10.108.182.101
KUBIA_PORT_443_TCP=tcp://10.108.182.101:443
KUBIA_PORT_443_TCP_PORT=443
KUBIA_SERVICE_PORT=80
KUBIA_PORT_80_TCP=tcp://10.108.182.101:80
KUBIA_PORT_443_TCP_ADDR=10.108.182.101
KUBIA_SERVICE_HOST=10.108.182.101
KUBIA_PORT_443_TCP_PROTO=tcp
KUBIA_PORT_80_TCP_PORT=80
...

service kubia에 관련된 환경변수들이 설정된 것을 볼 수 있다. KUBIA_SERVICE_HOST가 kubia service의 cluster-ip이고, KUBIA_SERVICE_PORT가 port이다. 또한, port이름 별로 각각 KUBIA_SERVICE_PORT_HTTP, KUBIA_SERVICE_PORT_HTTPS가 있는 것을 알 수 있다.

이러한 환경변수를 통해서 서비스에 접근할 수 있지만 사실 좋은 방법은 아니다. 왜냐하면 직접 IP:Port로 접근하는 것이 아니라, DNS로 접근하는 방법이 보다 더 쉽고, 클라이언트 입장에서 직관적이기 때문이다.

kubernetes의 kube-system namespace안에서는 기본적으로 kube-dns라는 서비스가 동작하고, 연결된 pod는 동일한 이름의 kube-dns이다. kube-dns 파드는 dns서버를 실행하며, 모든 pod들은 이 kube-dns service를 자동으로 사용하도록 구성된다. 이는 각 컨테이너의 /etc/resolv.conf파일을 수정해 이를 수행하는 것이다. 파드에서 실행 중인 프로세스에서 수행되는 모든 DNS 쿼리는 시스템에서 실행 중인 모든 서비스를 알고 있는 쿠버네티스 자체 DNS서버로 처리된다.

FQDN(정규화된 도메인 이름)을 통해서 service에 접근할 수 있는데, 다음과 같다.

{service-name}.{namespace}.svc.cluster.local

우리의 kubia service는 kubia라는 이름과 default라는 네임스페이스를 갖고 있기 때문에 다음과 같이 쓸 수 있다.

kubia.default.svc.cluster.local

service의 cluster-ip, port가 아니라, 해당 domain으로 요청하면 kubia service에 요청하게 되는 것이다.

물론, 이 역시도 클러스터 내부에서만 유효하므로 pod 내부에서 실행해야한다.

kubectl exec kubia-hw69s -- curl http://kubia-svc.default.svc.cluster.local -s
You've hit kubia-bbshd

다음과 같이 DNS를 통해서 클러스터 내의 서비스에 접근 가능한 것을 확인할 수 있다. 더 나아가, service명 자체만으로도 접근이 가능하다.

kubectl exec kubia-r54cp -- curl http://kubia-svc.default -s
You've hit kubia-bbshd

재밌는 것은 서비스 IP로는 ping을 보낼 수 없는데 이는 서비스의 cluster-IP가 가성 IP이므로 서비스 포트와 결합된 경우에만 의미가 있기 때문이다. 이에 대래서는 추후에 더 자세히 알아보도록 하자.

2. 클러스터 외부에 있는 서비스 연결

지금까지는 클러스터 내부에서만 접근 가능한 서비스를 만들었기 때문에 pod내부에서만 서비스로 접근이 가능했다.

실제 클러스터를 배포할 때는 서비스를 외부에 노출하여 접근할 수 있도록 해야할 때가 있다. 외부에서 서비스를 접근할 수 있도록 하는 방법은 몇 가지가 있다.
1. NodePort: NodePort 서비스의 경우 각 클러스터 노드는 노드 자체에서 포트를 열고 해당 포트로 수신된 트래픽을 서비스로 전달한다. 즉 node의 IP에서 port를 외부에 열어 해당 포트에 접근한 트래픽을 연결된 서비스에 흘려보내는 것이다. 또한 클러스터 IP와 포트로 클러스터 내부에서도 접근이 가능하다.
2. LoadBalancer: LoadBalancer는 클라우드 인프라에서 프로비저닝된 전용 서비스로, 트래픽을 모든 노드의 노드포트로 전달한다. 클라이언트는 IP로 서비스에 접근한다. 이는 클라우드 플랫폼을 사용할 때만 가능하다.
3. Ingress resource: Ingress는 4계층 서비스가 아니라 7계층 서비스를 제공하므로, TLS, mTLS 등과 같은 다양한 기능을 제공할 수 있다. 이에 대해서는 추후에 더 알아보도록 하자.

이 중 가장 쉬운 방법은 NodePort를 사용하여 노드의 port를 외부에 여는 것이다. NodePort서비스를 만들면 쿠버네티스는 모든 노드에 특정 포트를 할당하고(모든 노드에서 동일한 포트 번호가 사용됨) 서비스를 구성하는 파드로 들어오는 connection을 전달한다. 만약 노드의 IP가 192.168.10.29이고 port로 8080을 열었다면 192.168.10.29:8080으로 흘러보내진 트래픽은 NodePort서비스에 흘러들어가 연결된 파드에 전달되는 것이다.

노드포트 서비스를 생성하는 벙법은 다음과 같다.

  • kubia-svc-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
  name: kubia-nodeport
spec:
  type: NodePort
  selector:
    app: kubia
  ports:
  - port: 80
    targetPort: 8080
    nodePort: 30123

type 필드를 NodePort로 정하고 ports에서 노드에서 외부 포트로 열어줄 nodePort를 설정해주면 된다. 그러면 nodePort인 30123포트로 흘러온 트래픽이 서비스의 port80으로 흐르고, 이는 pod의 8080포트로 연결된다.

이제 nodePort를 생성하고 확인해보도록 하자.

kubectl create -f kubia-svc-nodeport.yaml
service/kubia-nodeport created

kubectl get svc
NAME             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
kubia-nodeport   NodePort    10.105.180.225   <none>        80:30123/TCP     2m2s
...

PORT(S)부분을 확인하면 80:30123/TCP로 쓰여있다. 이는 node의 30123포트가 외부에 열려있다는 것이다. 또한, 아까 말했듯이 NodePort는 모든 노드에서 포트가 열린다고 했다. 따라서 모든 노드의 30123포트가 열려있게 된다. 따라서, 해당 NodePort service에 접근할 수 있는 방법은 다음과 같다.

  1. cluster-ip인 10.105.180.225:80으로 접근
  2. 첫번째 노드IP:30123
  3. 두번째 노드IP:30123
    첫번째 노드IP:30123으로 트래픽을 흘려보내면, node1에만 있는 pod에 트래픽이 전담으로 가는 것이 아니라, node1, node2에 있는 pod에 랜덤하게 연결된다.

필자의 경우 node1에 대한 ip를 설정해놓았다.

cat /etc/hosts
127.0.0.1 localhost

172.31.13.93 master
172.31.2.117 node1
172.31.4.174 node2

요청을 보내보도록 하자.

curl node1:30123
You've hit kubia-r54cp

응답이 온 것을 확인할 수 있다. 이처럼 nodeip:port로 외부에 서비스가 연결된 것을 확인할 수 있다.

로드밸런서를 이용해서 외부 서비스를 노출하는 방법은 다루지 않는다. 로드밸런서는 EXTERNAL-IP가 따로 존재하고 port를 외부에 노출시켜 EXTERNAL-IP:PORT로 외부에서 접근할 수 있도록 해준다. 즉, 노드IP:Port가 아니라, 제 3의 다른 IP를 사용하는 것이다. 다만, 로드밸런서는 클라우드 공급자 이외에 사용할 수 없다.

3. Ingress

마지막으로 Ingress resource이다. 로드밸런서는 public IP를 통해 포트마다 서비스를 열어야하는 불편함이 있지만 ingress의 경우는 한 IP로 수십 개의 서비스에 접근이 가능하도록 지원해준다. 이는 요청한 호스트와 경로에 따라 요청을 전달할 서비스를 결정하기 때문이다.

ingress는 application layer에서 HTTP로 작동하기 때문에 4계층에서 동작 중인 서비스에서는 없는 쿠키 기반의 세션 어피니티 등과 같은 기능을 제공할 수 있다.

ingress object는 사실 클러스터 외부에서 내부로 접근하는 요청을 어떻게 처리할 지 정의한 규칙일 뿐이다. 이 규칙에 따라 트래픽을 분산하는 것이 바로, ingress controller이다. 따라서 ingress controller가 먼저 있어야 ingress object가 동작하게 된다. ingress controller는 기본적으로 kubernetes cluster에 설치되지 않으므로, 설치가 필요하다. https://kubernetes.github.io/ingress-nginx/deploy/

  • ingress nginx controller 설치 방법
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/cloud/deploy.yaml

위의 명령어를 사용하면 다음의 결과가 나오게 된다.

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/cloud/deploy.yaml

namespace/ingress-nginx created
serviceaccount/ingress-nginx created
serviceaccount/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
configmap/ingress-nginx-controller created
service/ingress-nginx-controller created
service/ingress-nginx-controller-admission created
deployment.apps/ingress-nginx-controller created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created
ingressclass.networking.k8s.io/nginx created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created

kubectl get po -n ingress-nginx
NAME                                        READY   STATUS      RESTARTS   AGE
ingress-nginx-admission-create-sblll        0/1     Completed   0          59s
ingress-nginx-admission-patch-p72x7         0/1     Completed   0          59s
ingress-nginx-controller-8558859656-lc2wb   1/1     Running     0          59s

ingress-nginx-controller pod가 생성된 것을 볼 수 있다. ingress controller는 service와 pod 등 각종 kubernetes resource를 관리하는 하나의 object인데, ingress resource와의 관계는 다음과 같다.


       <--kubia.example.com/kubia <--> |-----------ingress-controller-----------| <--> service1 <--> pod1
client <--kubia.example.com/foo   <--> | service(NodePort, LoadBalancer) -> pod | <--> service2 <--> pod2
       <--foo.example.com         <--> | -> ingress rule                        | <--> service3 <--> pod3
       <--bar.example.com         <--> |----------------------------------------| <--> service4 <--> pod4

client는 호스트와 path와 따라 외부에 노출된 서비스(NodePort, LoadBalancer)에 요청을 보낸다. 이때 service는 ingress-controller가 관리하는 service로 해당 service로 라우팅된 요청은 ingress-controller가 만들어낸 ingress-controller pod에 요청이 전달된다. pod에서는 ingress resource를 읽어서 정해진 rule에 따라 맵핑된 service로 트래픽을 전달한다.

이제 Ingress resource를 만들어보도록 하자.

  • kubia-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: kubia-ingress
spec:
  ingressClassName: nginx
  rules:
  - host: kubia.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: kubia-nodeport
            port: 
              number: 80

ingressClassName는 ingress controller의 class이름을 적어주어야 한다.

kubectl get ingressclasses.networking.k8s.io
NAME    CONTROLLER             PARAMETERS   AGE
nginx   k8s.io/ingress-nginx   <none>       50m

nginx로 ingressclass resource가 있는 것을 확인할 수 있다. 이를 ingress yaml파일의 ingressClassName에 적어주면 된다.

rules가 ingress에 적용할 규칙들을 말한다. host로는 kubia.example.com로 접근하고 path/이다. 해당 호스트와 path조건이 맞으면 ingress controller는 kubia-nodeport의 80포트로 트래픽을 흘려보낸다.

kubectl create -f kubia-ingress.yaml
Error from server (InternalError): error when creating "kubia-ingress.yaml": Internal error occurred: failed calling webhook "validate.nginx.ingress.kubernetes.io": failed to call webhook: Post "https://ingress-nginx-controller-admission.ingress-nginx.svc:443/networking/v1/ingresses?timeout=10s": context deadline exceeded

ingress를 만들어내면 다음과 같은 에러가 생길 수 있다. 이는 ingress에 대한 spec사항이 변경되면서 ingress-nginx-controller에서 이를 반영하지 않아 발생하는 문제로 확인되고 있다. 따라서, validation에 쓰이는 일부 오브젝트를 삭제할 필요가 있다.

kubectl delete -A ValidatingWebhookConfiguration ingress-nginx-admission
validatingwebhookconfiguration.admissionregistration.k8s.io "ingress-nginx-admission" deleted

kubectl create -f kubia-ingress.yaml
ingress.networking.k8s.io/kubia created

validatingwebhookconfiguration을 삭제 후 ingress을 생성하려고 하면 성공적으로 생성된다. 사실 왜 이런 오류가 발생하는 지에 대해서는 잘 모르겠다. ingress-nginx-controller가 실제 배포에 쓰이지 않아, 이런저런 오류들이 많이 쌓여있는 것 같다. 실무에서는 nginx-ingress-controller 또는 istio, kong ingress controller들을 사용하도록 하자.

잘 만들어졌는 지 확인해보도록 하자.

kubectl get ingress
NAME    CLASS   HOSTS               ADDRESS   PORTS   AGE
kubia   nginx   kubia.example.com             80      4m35s

제대로 만들어진 것을 확인하였다.

이제 요청을 보내야하는데, 필자의 경우는 EC2 내부에 kubernetes cluster를 구축한 것이기 때문에 IngressController가 LoadBalancer와 연결되지 않았기 때문에 외부 IP가 없다. 또한, NodePort로도 열지 않았기 때문에, 따로 port forwarding으로 ingress-nginx-controller의 외부 서비스를 만들어주도록 하자.

kubectl port-forward --namespace=ingress-nginx service/ingress-nginx-controller 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

ingress-nginx-controller를 외부에서 접근할 수 있도록 포트 포워딩시켰으므로, 다른 터미널을 열어서 ingress 정의에 따라 요청을 보내보도록 하자.

curl http://localhost:8080 -H "host: kubia.example.com"
You've hit kubia-g9wql

8080으로 ingress-nginx-controller에 대한 service를 외부에 열었으므로, 다음과 같이 접근할 수 있는 것이다. ingress-nginx-controller는 들어온 호스트와 path를 바탕으로 ingress Rule과 비교하여 어떤 service로 들어온 트래픽을 전달할 지 결정하는 것이다.

ingress에는 여러 가지 rule을 결정할 수 있는데, 동일한 호스트에 서로 다른 경로를 매핑할 수도 있다. 현재의 경우 필요한 서비스들을 만들지 않아서 동작하진 않지만 다음과 같이 만들 수 있다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: kubia-ingress
spec:
  ingressClassName: nginx
  rules:
  - host: kubia.example.com
    http:
      paths:
      - path: /kubia
        pathType: Prefix
        backend:
          service:
            name: kubia-nodeport
            port: 
              number: 80
      - path: /bar
        pathType: Prefix
        backend:
          service:
            name: bar
            port: 
              number: 80

이 경우 kubia.example.com라는 같은 호스트를 가지지만 path가 /kubia, /bar로 다르다. 따라서 다음과 같이 요청을 보내야한다.

curl http://localhost:8080/kubia -H "host: kubia.example.com"
curl http://localhost:8080/bar -H "host: kubia.example.com"

이와 같이 ingress는 같은 호스트에 다양한 path를 가질 수 있으며, 반대로 같은 path에 다양한 host를 가질 수 있도록 rule을 정할 수 있다. 더불어 ingress는 http 계층에서 동작하기 때문에 TLS를 설정할 수 있다. 그러나 일반적으로 ingress는 cluster내부의 서비스를 통해 백엔드 pod에게 traffic을 흘려보내는 것이므로 굳이 TLS를 지원하지 않아도 된다. 다만 ingress-controller의 경우는 외부에 노출되기 때문에 TLS를 제공하여 HTTPS로 접근하도록 만드는 것이 좋다.

4. Readiness Probe

readiness probe는 liveness probe와 마찬가지로 pod의 컨테이너를 확인하는 로직이다. liveness probe가 container가 제대로 동작하고 있는 지를 확인하기 위함이라면 readiness probe는 pod의 container가 준비가 되어있는 지 확인하기 위함이다.

pod가 실행되고 서버가 실행되며 일련의 데이터를 처리하는 로직이 필요할 수 있다. 이러한 setup이 이루어지기 전에 service에 의해 외부의 요청이 전달되면 일련의 setup과정에서 큰 오류를 만들 수 있다. 이런 일이 없도록 readiness probe는 개발자가 지정한 작업을 수행하여 readiness가 준비되었다고 판단할 때, service로 부터 traffic을 받는다. 즉, readiness probe에서 작업이 완료되지 않으면 service에서 해당 pod를 연결하지 않는다는 것이다.

재밌는 것은 liveness probe는 pod의 liveness에 문제가 생기면 pod를 계속해서 죽였다 살린다. 즉, 새로운 pod로 갈아치운다. 반면에 readiness probe는 readiness check에서 정상성이 확보되지 않아도 pod를 죽이지 않고, service와의 연결을 끊는다. 이후에 다시 readiness probe에 의해 readiness check가 성공한다면 service에 연결하여 외부의 traffic을 받을 수 있게 된다. 따라서 3개의 pod에서 1개만이 readiness probe에 실패하였다면 1개만 service에 연결되지 않고 나머지 2개는 연결된다는 것이다. 이후 시간이 지나 readiness probe에 실패했던 container가 성공하면 pod를 service에 연결한다.

readiness probe의 유형은 liveness probe와 마찬가지로 3가지가 있다.

  1. exec probe로 특정 프로세스를 실행한 다음 종료 상태 코드로 결정한다.
  2. http get probe는 특정 path로 get요청을 보내어 상태 코드로 컨테이너 준비 여부를 결정한다.
  3. TCP socket probe는 지정된 포트로 TCP연결을 하여 성공하면 준비되었다고 간주한다.

이전에 만들었던 ReplicaSet에 readinessProbe를 설정해주도록 하자.

  • kubia-replicaset.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: kubia
spec:
  replicas: 3
  selector:
    matchLabels:
      app: kubia
  template:
    metadata:
      labels:
        app: kubia
    spec:
      containers:
      - name: kubia
        image: luksa/kubia
        ports:
          - name: http
            containerPort: 8080
          - name: https
            containerPort: 8443
        readinessProbe:
            exec:
              command:
                - ls
                - /var/ready

다음의 yaml파일에서 containers.readinessProbe부분을 확인할 수 있다. exec을 사용하여 명령어를 실행하도록 하였는데, ls /var/ready를 실행하도록 하는 것이다. 기본적으로 /var/ready는 없는 디렉터리이므로 readiness probe에 실패하게된다. 다음의 ReplicaSet을 만들어보도록 하자.

kubectl create -f ./kubia-replicaset.yaml
replicaset.apps/kubia created

kubectl get po -A
NAMESPACE          NAME                                        READY   STATUS      RESTARTS      AGE
default            kubia-8qxmr                                 0/1     Running     0             4s
default            kubia-bk96x                                 0/1     Running     0             4s
default            kubia-jkk7t                                 0/1     Running     0             4s

READY부분에 0/1이 표시된 것을 볼 수 있다. 아직 readiness probe의 check에서 실패한 것이기 때문이다. 이 상태에서는 위에서 ingress에 연결된 ingress controller로 요청을 보내어도 응답이 오지 않는다. 이유는 kubia-nodeport에 연결된 kubia-* pod가 없기 때문이다. 이는 readiness check에 실패하였기 때문에 service에 pod가 등록되지 않은 것이다.

ready상태로 만들기 위해서 pod에 접근해 /var/ready 디렉터리르 만들어주도록 하자.

kubectl exec kubia-8qxmr -- touch /var/ready

다음으로 시간이 지난 뒤 정말 ready상태가 되었는 지 확인해보도록 하자.

kubectl get po
NAME                   READY   STATUS    RESTARTS   AGE
demo-b66f56cf5-qg9wv   1/1     Running   0          64m
kubia-8qxmr            1/1     Running   0          51s
kubia-bk96x            0/1     Running   0          51s
kubia-jkk7t            0/1     Running   0          51s

READY1/1이 된 것을 확인할 수 있다! 이제 ingress-controller를 통한 요청에도 문제없이 응답이 올 것이다. 이는 kubia-nodeport svc에 pod가 연결되었기 때문이다.

위의 예제는 readiness probe를 설정하기 위한 예제일 뿐이며, 실제로는 서버가 client 요청을 받을 수 있을 단계가 되면 특정 path로 GET요청을 보내도록 readiness probe에게 만들면 된다. 이는 전적으로 개발자의 몫이다.

두 가지 주의해야할 사항이 있다.
1. readiness probe는 선택이 아닌 필수다. 만약 readiness probe를 만들지 않으면 pod가 시작하자마자 service에 연결되어 endpoint로 등록되는데, 이는 pod가 client의 요청을 받지 못할 초기 init 상황에서 client는 Connection refused 에러를 받을 수 있기 때문이다.
2. pod가 종료될 때 연결된 service들에 대해서 연결을 종료하기 위해 readiness probe에 exit code를 넣을 수 있다. 그러나 이러한 짓은 하지 말도록 하자. 어차피 pod가 종료되면 연결된 서비스들에 대한 endpoint등록이 모두 말소되기 때문에 사후 처리를 신청쓰지 않아도 된다.

0개의 댓글