backend/kubernetes in action

[kubernetes in action] 5-1. Introducing Services

seul chan 2020. 6. 5. 21:13

Ch 5. Services: enabling clients to discover and talk to pods

이 챕터는 다음 내용을 다룬다.

  • Service 리소스를 만들고 한 주소로 pod 그룹을 expose
  • 클러스터 안의 services를 expose
  • 외부 서비스를 클러스터 안의 service와 연결
  • service의 부분으로 pod가 준비되게 컨트롤
  • Troubleshooting service

요즘의 많은 애플리케이션들은 외부 요청에 응답하게 만들어진다.

kubernetes가 아닌 상황에서는 sysadmin이 클라이언트 앱에 각각의 IP주소나 hostname을 부여하지만, Kubernetes에서는 이런 방식으로 작동하지 않는다.

  • Pod는 ephemeral (덧없다, 단명한다): 언제든지 생기고 사라질 수 있다
  • Kubernetes는 pod가 시작하기 전에, 그리고 pod가 노드에 스케쥴된 이후에 pod의 IP 주소를 부여한다. 따라서 client는 pod의 IP 주소를 찾을 수 없다
  • 수평적 스케일링은 여러개의 pod들이 같은 서비스를 제공하는 것을 의미한다: 각각의 pod들은 서로 다른 IP 주소를 갖는데 클라이언트는 이들을알 필요가 없다. 대신, 모든 pod들은 하나의 IP 주소를 통해 접근 가능해야 한다.

이런 문제를 해결하기 위해 Kubernetes는 Service라는 resource type을 제공한다.

5.1. Introducing Services

Kubernetes Service는 같은 서비스를 제공하는 pod의 그룹에 하나의 지속적인 접속점을 제공해주는 resource이다. 각각의 service는 존재하는 동안 절대 변하지 않는 IP주소와 port를 가진다.

클라이언트는 해당 주소로 pod 그룹 중 하나에 접근 가능하고, 개별 pod의 주소에는 신경을 쓸 필요가 없기 때문에 pod가 언제든지 옮겨질 수 있게 된다.

Creating services

service에서 load-balance 할 특정 pod는 이전에서 봤던 것과 비슷하게 label selector를 사용하여 정한다.

가장 쉽게 service를 만드는 방법은 kubectl expose 명령어를 사용하는 것이다. expose 명령어는 같은 pod selector를 기반으로 Service 리소스를 만들고 하나의 IP 주소와 포트로 노출시켜준다

이번에는 expose 명령어 대신 YAML 파일로 Kubernetes API를 사용하여 service를 직접 만들어보자.

kubia-svc.yaml에 다음 내용을 추가

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

kubia라는 service를 만들어 80 port를 받게 하고, app=kubia label selector를 가진 pod들에게 8080 포트로 라우팅 해준다는 것을 의미

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

$ kubectl get svc
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP   8d
kubia        ClusterIP   10.97.60.204   <none>        80/TCP    7s

위의 IP는 cluster IP이기 때문에 cluster 내부에서만 접속 가능하다. 외부 노출은 조금 이따가 다룰 것이다.

클러스터 안에서 서비스에 요청을 보내는 데에는 몇 가지 방법이 있는데

  • 해당 cluster ip에 request를 보내는 pod를 만드는 것
  • kubernetes node에 ssh로 접속 후에 curl 명령어 사용
  • kubectl exec 명령어로 존재하는 pod에 접속 후 curl 명령어 사용

마지막 옵션을 사용하여 어떻게 존재하는 pod에서 명령어를 실행시키는지도 배워보자.

kubectl exec 명령어를 사용하면 pod 안의 container에 자유롭게 명령어를 보낼 수 있다.

kubectl get pods 명령어로 나온 pod중 타겟 pod를 선택한 다음 exec 명령어를 실행시켜보자.

export POD_NAME=$(kubectl get pod -l app=<app_label> -o jsonpath="{.items[0].metadata.name}")과 같이 변수로 등록시켜서 해도 편리하다.

$ kubectl get pods
NAME                                               READY   STATUS      RESTARTS   AGE
kubia-frqgr                                        1/1     Running     0          18s
kubia-t7c5f                                        1/1     Running     0          18s
kubia-zdzn9                                        1/1     Running     0          18s

# 첫 번 째 pod를 골라서 exec 명령어를 실행시켰다.
$ kubectl exec kubia-frqgr -- curl -s http://10.97.60.204
You've hit kubia-t7c5f

double dash (--)는 무엇일까? -- 이후의 모든 명령어는 pod 안에서 실행된다고 명시. argument가 없으면 따로 적지 않아도 되지만 위에서는 -s 인자가 있기 때문에 --가 없다면 exec의 인자로 인식하여 오류가 난다.

service proxy가 랜덤으로 pod에 포워딩하기 때문에 만약 명령어를 여러 번 실행한다면 다른 pod가 출력될 것이다.

만약 특정 클라이언트가 매번 같은 pod에 요청을 보내고 싶다면 sessionAffinity property를 ClientIP로 설정하면 된다. (default는 None)

kubernetes는 sessionAffinityNoneClientIP만 제공

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

실제 수정해보려면 kubectl edit -f kubia-svc.yaml로 바로 수정 가능하다.

Exposing multiple ports in the same service

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

여러 port들에게 각각 port name을 줄 수 있는데, 이렇게 사용하면 port number를 변경하더라고 service spec을 따로 변경할 필요가 없다. (helm 배포가 필요 없어짐)

Discovering services

kubernetes에서는 client pod가 service의 IP와 port를 발견할 수 있는 방법을 제공

pod가 실행되면 해당 시점에 존재하는 service를 환경 변수로 갖는다.

이를 확인하기 위해 기존 pod를 지워보자

$ kubectl delete po --all
...

그러면 현재 존재하는 replicaSet이 다시 pod를 띄울 것이다. 새롭게 뜬 pod의 env를 확인해보자.

$ kubectl exec kubia-766tj env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=kubia-766tj
KUBERNETES_PORT_443_TCP_PORT=443
KUBIA_SERVICE_HOST=10.100.198.79
KUBIA_PORT_443_TCP_PROTO=tcp
KUBIA_PORT_443_TCP_ADDR=10.100.198.79
...

kubia service의 post와 port를 볼 수 있다.

환경변수는 service의 IP와 port를 찾는 방법 중 하나이다. 하지만 보통은 DNS domain을 사용하는데 왜 Kubernetes는 이를 사용하지 않는 것일까? 사실은 사용한다!

Discovering services through DNS

3장에서 우리는 kube-system namespace의 pod를 리스팅 하여 그 중 kube-dns를 보았다.

kubectl get po --namespace kube-system으로 다시 확인 가능하다.

$ kubectl exec -it kubia-766tj -- bash
root@kubia-766tj:/# curl http://kubia.default.svc.cluster.local
You've hit kubia-qtfdt

root@kubia-766tj:/# curl http://kubia.default
You've hit kubia-qtfdt

root@kubia-766tj:/# curl http://kubia
You've hit kubia-qtfdt

root@kubia-766tj:/# cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5