쿠버네티스의 서비스는 동일한 서비스를 제공하는 파드 그룹에 지속적인 단일 접점을 만들려고 할 때 생성하는 리소스다. 각 서비스는 서비스가 존재하는 동안 절대 바뀌지 않는 ip주소와 포트가 있다. 클라이언트는 해당 ip와 포트로 접속한 다음 해당 서비스를 지원하는 파드 중 하나로 연결된다. 이런 방식으로 서비스의 클라이언트는 서비스를 제공하는 개별 파드의 위치를 알 필요 없으므로, 이 파드는 언제든지 클러스터 안에서 이동할 수 있다.
서비스 연결은 서비스 뒷단의 모든 파드로 로드밸런싱된다. 어떤 파드가 서비스의 일부분인지 아닌지를 정의하는 방법은 레이블 셀렉터를 이용하는 것이다.
이 전 장에서 node.js 애플리케이션이 포함된 파드의 세 개의 인스턴스를 실행하는 레플리케이션컨트롤러를 만들었다. 레플리케이션컨트롤러를 다시 생성하고 이 세 개에 관한 서비스를 만들어 보자.
kubia-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
ports:
- port: 80 # 서비스가 사용할 포트
targetPort: 8080 # 서비스가 포워드할 컨테이너 포트
selector: # app=kubia 레이블이 있는 모든 파드가 이 서비스에 포함된다.
app: kubia
kubectl create를 사용해 yaml 파일을 게시해 서비스를 생성하자.
$ kubectl create -f kubia-svc.yaml
service/kubia created
YAML을 게시한 후 네임스페이스의 모든 서비스 리소스를 조회하고 서비스에 내부 클러스터 IP가 할당됐는지 확인할 수 있다.
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.36.0.1 <none> 443/TCP 20h
kubia ClusterIP 10.36.8.134 <none> 80/TCP 13s
서비스에 할당된 IP 주소를 보여준다. 클러스터 IP 이므로 클러스터 내부에서만 액세스할 수 있다. 서비스의 기본 목적은 파드 그룹을 클러스터의 다른 파드에 노출시키는 것이지만 대개 서비스를 외부로 노출하기를 원할 것이다.
몇 가지 방법으로 클러스터 내에서 서비스로 요청을 보낼 수 있다.
kubectl exec 명령어를 사용하면 기존 파드의 컨테이너 내에서 원격으로 임의의 명령어를 실행할 수 있다.
컨테이너의 내용, 상태, 환경을 검사할 때 유용하다.
$ kubectl exec kubia-5rpz4 -- curl -s http://10.36.8.134
You've hit kubia-95xvv
명령을 실행했을 때 파드의 컨테이너 내에세 curl 명령을 실행하도록 쿠버네티스에 지시했다. curl은 HTTP 요청을 서비스 IP로 보냈다. 이 IP에는 세 개의 파드가 연결돼 있다.
쿠버네티스 서비스 프록시가 연결을 가로채서 세 개의 파드 중 임의의 파드로 요청을 전달한다. 해당 파드 내에서 실행 중인 node.js는 요청을 처리하고 해당 파드의 이름을 포함하는 HTTP 응답을 반환한다.
curl은 표준 출력으로 응답을 출력하고 이를 kubectl이 있는 로컬 시스템의 표준 출력에 다시 표시한다.
특정 클라이언트의 모든 요청을 매번 같은 파드로 리다이렉션하려면 서비스의 세션 어피니티 속성을 기본값 None 대신 ClientIP로 설정한다.
apiVersion: v1
kind: Service
spec:
sessionAffinity: ClientIP
...
서비스는 단일 포트만 노출하지만 여러 포트를 지원할 수도 있다. 예를 들어 파드가 두 개의 포트를 수신한다면 하나의 서비스를 사용해 포트 80과 443을 파드의 포트 8080과 8443으로 전달할 수 있다.
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
port:
- name: http # 포트 80은 파드의 포트 8080에 매핑된다
port: 80
targetPort: 8080
- name: https # 포트 443은 파드의 포트 8443에 매핑된다
port: 443
targetPort: 8443
selector: # 레이블 셀렉터는 항상 모든 서비스에 적용된다.
app: kubia
지금까지는 대상 포트를 번호로 참조했지만 각 파드의 포트에 이름을 지정하고 서비스 스펙에서 이름으로 참조할 수도 있다.
kind: Pod
spec:
containers:
- name: kubia
ports:
- name: http # 컨테이너 포트 8080은 http라고 한다.
containerPort: 8080
- name: https # 컨테이너 포트 8443은 https라고 한다.
containerPort: 8443
파드를 정의하고 나면, 서비스 스펙에서 이름으로 해당 포트를 참조할 수 있다.
apiVersion: v1
kind: Service
spec:
ports:
- name: http # 포트 80은 컨테이너 포트와 매핑된다
port: 80
targetPort: http
- name: https # 포트 443은 컨테이너 포트와 매핑된다
port: 443
targetPort: https
서비스를 만들면 파드에 액세스 할 수 있는 안정적인 IP 주소와 포트가 생긴다. 이 주소는 서비스가 유지되는 동안 변경되지 않는다.
클라이언트 파드는 서비스의 IP와 포트를 어떻게 알 수 있을까?
파드가 시작되면 쿠버네티스는 해당 시점에 존재하는 각 서비스를 가리키는 환경변수 세트를 초기화한다. 클라이언트 파드를 생성하기 전에 서비스를 생성하면 해당 파드의 프로세스는 환경변수를 검사해 서비스의 IP 주소와 포트를 얻을 수 있다.
서비스에 대한 환경변수를 보려면 먼저 모든 파드를 삭제하고 레플리케이션컨트롤러에서 새로 파드를 만들어야 한다.
$ kubectl delete po --all
pod "kubia-5rpz4" deleted
pod "kubia-95xvv" deleted
pod "kubia-kf846" deleted
$ kubectl exec kubia-fgrhp env
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=kubia-fgrhp
NPM_CONFIG_LOGLEVEL=info
NODE_VERSION=7.9.0
YARN_VERSION=0.22.0
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_ADDR=10.36.0.1
KUBIA_SERVICE_HOST=10.36.8.134
KUBIA_PORT=tcp://10.36.8.134:80
KUBIA_PORT_80_TCP_PORT=80
KUBERNETES_PORT=tcp://10.36.0.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBIA_SERVICE_PORT=80
KUBIA_PORT_80_TCP=tcp://10.36.8.134:80
KUBIA_PORT_80_TCP_ADDR=10.36.8.134
KUBERNETES_SERVICE_HOST=10.36.0.1 # 서비스의 클러스터 IP다
KUBERNETES_SERVICE_PORT_HTTPS=443 # 서비스가 제공하는 포트다
KUBERNETES_PORT_443_TCP=tcp://10.36.0.1:443
KUBIA_PORT_80_TCP_PROTO=tcp
HOME=/root
파드에서 실행 중인 프로세스에서 수행된 모든 DNS 쿼리는 시스템에서 실행 중인 모든 서비스를 알고 있는 쿠버네티스의 자체 DNS 서버로 처리한다.
각 서비스는 내부 DNS 서버에서 DNS 항목을 가져오고 서비스 이름을 알고 있는 클라이언트 파드는 환경변수 대신 FQDN으로 액세스 할 수 있다.
FQDN으로 kubia 서비스에 액세스하자. 기존 내의 파드 내에서 수행해야한다. kubectl exec curl 를 사용해 파드의 컨테이너에서 명령어를 실행했었지만 이번에는 bash 셸을 실행해 컨테이너에서 명령을 실행해보자.
$ kubectl exec -it kubia-fgrhp bash
이제 컨테이너 안으로 들어왔다. curl 명령어를 사용해 다음 방법으로 kubia 서비스에 액세스할 수 있다.
root@kubia-fgrhp:/# curl http://kubia.default.svc.cluster.local
You've hit kubia-x7pq8
여기서 kubia = 서비스이름, default = 서비스가 정의된 네임스페이스, svc.cluster.local = 모든 클러스터의 로컬 서비스 이름에 사용되는 클러스터의 도메인 접미사다.
동일한 네임스페이스에 있는 경우 접미사와, 네임스페이스는 생략할 수 있다.
root@kubia-fgrhp:/# curl http://kubia
You've hit kubia-xgws7
root@kubia-fgrhp:/# ping kubia
PING kubia.default.svc.cluster.local (10.36.8.134): 56 data bytes
^C--- kubia.default.svc.cluster.local ping statistics ---
67 packets transmitted, 0 packets received, 100% packet loss
서비스로 curl은 동작하지만 핑은 응답이 없다. 이는 서비스의 클러스터 IP가 가상 IP 이므로 서비스 포트와 결합된 경우에만 의미가 있기 때문이다.
쿠버네티스 서비스 기능으로 외부 서비스를 노출하려는 경우, 서비스가 클러스터 내에 있는 파드로 연결을 전달하는 게 아니라, 외부 IP와 포트로 연결을 전달하는 것이다.
이 경우 서비스 로드밸런싱과 서비스 검색 모두 활용할 수 있다. 클러스터에서 실행 중인 클라이언트 파드는 내부 서비스에 연결하는 것처럼 외부 서비스에 연결할 수 있다.
서비스는 파드에 직접 연결되지 않는다. 대신 엔드포인트 리소스가 그 사이에있다.
kubectl describe 명령을 사용하면 엔드포인트를 확인할 수 있다.
$ kubectl describe svc kubia
Name: kubia
Namespace: default
Labels: <none>
Annotations: cloud.google.com/neg: {"ingress":true}
Selector: app=kubia # 서비스의 파드 셀렉터는 엔드포인트 목록을 만드는 데 사용된다.
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.36.8.134
IPs: 10.36.8.134
Port: <unset> 80/TCP
TargetPort: 8080/TCP
Endpoints: 10.32.1.11:8080,10.32.1.12:8080,10.32.2.16:8080 # 이 서비스의 엔드포인트를 나타내는 파드 IP와 포트 목록
Session Affinity: None
Events: <none>
엔드포인트 리소스는 서비스로 노출되는 파드의 IP 주소와 포트목록이다.
kubectl get을 사용해 기본 정보를 표시 할 수 있다.
$ kubectl get endpoints kubia
NAME ENDPOINTS AGE
kubia 10.32.1.11:8080,10.32.1.12:8080,10.32.2.16:8080 16m
클라이언트가 서비스에 연결하면 서비스 프록시는 이들 중 하나의 IP와 포트 쌍을 선택하고 들어온 연결을 대상 파드의 수신 대기 서버로 전달한다.
수동으로 관리되는 엔드포인트를 사용해 서비스를 만들려면 서비스와 엔드포인트 리소스를 모두 만들어야한다.
external-service.yaml
apiVersion: v1
kind: Service
metadata:
name: external-service # 서비스의 이름은 엔드포인트 오브젝트 이름과 일치해야 한다.
spec: # 이 서비스에는 셀렉터가 정의되어있지 않다.
ports:
- port: 80
포트 80으로 들어오는 연결을 허용하는 external-service라는 서비스를 정의했다.
이제 엔트포인트 리소스를 생성한다.
external-service-endpoints.yaml
apiVersion: v1
kind: Endpoints
metadata:
name: external-service # 엔드포인트 이름은 서비스 이름과 일치해야 한다.
subsets:
- addresses: # 서비스가 연결을 전달할 엔드포인트 IP
- ip: 11.11.11.11
- ip: 22.22.22.22
ports:
- port: 80 # 엔드포인트의 대상 포트
서비스와 엔드포인트 리소스가 모두 서버에 게시되면 파드 셀렉터가 있는 일반 서비스처럼 서비스를 사용할 수 있다.
서비스가 만들어진 후 만들어진 컨테이너에는 서비스의 환경변수가 포함되며 IP:포트 쌍에 대한 모든 연결은 서비스 엔드포인트 간에 로드밸런싱한다.
서비스의 엔드포인트를 수동으로 구성해 외부 서비스를 노출하는 대신 좀더 간단한 방법으로 FQDN으로 외부 서비스를 참조 할 수 있다.
외부 서비스 별칭으로 사용되는 서비스를 만들려면 유형필드를 ExternalName으로 설정해 서비스 리소스를 만든다.
api.somecompany.com에 공개 API가 있다고 가정하고, 다음과 같이 표시된 대로 이를 가리키는 서비스를 정의할 수 있다.
external-service-externalname.yaml
apiVersion: v1
kind: Service
metadata:
name: external-service
spec:
type: ExternalName # 서비스 유형이 ExternalName으로 설정된다.
externalName: api.somecompany.com # 실제 서비스의 정규화된 도메인 이름
ports:
- port: 80
서비스가 생성되면 파드는 서비스의 FQDN을 사용하는 대신 external-service.default.svc.cluster.local 도메인 이름으로 외부 서비스에 연결 할 수 있다.
이렇게 하면 서비스를 사용하는 파드에서 실제 서비스 이름과 위치가 숨겨져 나중에 externalName 속성을 변경하거나 유형을 다시 ClusterIP로 변경하고 서비스 스펙을 만들어 서비스 스펙을 수정하면 나중에 다른 서비스를 가리킬 수 있다.
외부에서 서비스를 액세스할 수 있는 몇 가지 방법이 있다.
노드포트로 서비스 유형 설정 : 노드포트 서비스의 경우 각 클러스터 노드는 노드 자체에서 포트를 열고 해당 포트로 수신된 트래픽을 서비스로 전달한다. 서비스는 내부 클러스터 IP와 포트로 액세스할 수 있을 뿐만 아니라 모든 노드의 전용 포트로도 액세스할 수 있다.
서비스 유형을 노드포트 유형의 확장인 로드밸런서로 설정 : 쿠버네티스가 실행 중인 클라우드 인프라에서 프로비저닝된 전용 로드밸런서로 서비스에 액세스할 수 있다. 로드밸런서는 트래픽을 모든 노드의 노드포트로 전달한다. 클라이언트는 로드밸런서의 IP로 서비스에 액세스한다.
단일 IP 주소로 여러 서비스를 노출하는 인그레스 리소스 만들기 : HTTP 레벨에서 작동하므로 4게층 서비스 보다 더 많은기능 제공할 수 있다.
노드포트 서비스를 만들면 쿠버네티스는 모든 노드에 특정 포트를 할당하고 서비스를 구성하는 파드로 들어오는 연결을 전달한다. 내부 클러스터 IP뿐만 아니라 모든 노드의 IP와 할당된 노드포트로 서비스에 액세스할 수 있다.
kubia-svc-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: kubia-nodeport
spec:
type: NodePort # 서비스 유형을 노드포트로 설정
ports:
- port: 80 # 서비스 내부 클러스터 IP의 포트
targetPort: 8080 # 서비스 대상 파드의 포트
nodePort: 30123 # 각 클러스터 노드의 포트 30123으로 서비스에 액세스할 수 있다.
selector:
app: kubia
서비스를 생성하고 정보를 살펴보자
$ kubectl create -f kubia-svc-nodeport.yaml
service/kubia-nodeport created
$ kubectl get svc kubia-nodeport
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubia-nodeport NodePort 10.36.11.131 <none> 80:30123/TCP 15s
EXTENAL-IP 열은 [nodes]라고 표시돼 있고 클러스터 노드의 IP 주소로 서비스에 액세스할 수 있음을 나타낸다. PORT 열에는 클러스터 IP 내부포트 80과 노드포트 30123이 모두 표시된다.
노드포트로 서비스에 액세스하려면 해당 노드포트에 대한 외부 연결을 허용하도록 방화벽을 구성해야 한다.
구글 클라우드 플랫폼 방화벽 설정 작업을 수행해보자.
$ gcloud compute firewall-rules create kubia-svc-rule --allow=tcp:30123
Creating firewall...working..Created [https://www.googleapis.com/compute/v1/projects/bright-vision-360512/global/firewalls/kubia-svc-rule].
Creating firewall...done.
NAME: kubia-svc-rule
NETWORK: default
DIRECTION: INGRESS
PRIORITY: 1000
ALLOW: tcp:30123
DENY:
DISABLED: False
이제 노드 IP와 포트 30123으로 서비스에 액세스할 수 있다. 먼저 노드 IP를 알아야한다.
노드의 JSON 또는 YAML 요약에서 IP를 찾을수 있다. kubectl에게 노드 IP만 출력하도록 해보자
$ kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="ExternalIP")].address}'
35.247.77.246 34.145.104.124 35.230.51.22
노드의 IP를 알고나면 서비스에 액세스 할 수 있다.
$ curl http://35.247.77.246:30123
You've hit kubia-x7pq8
$ curl http://34.145.104.124:30123
You've hit kubia-xgws7
$ curl http://35.230.51.22:30123
You've hit kubia-xgws7
보통 클라우드 공급자에서 실행되는 쿠버네티스 클러스터는 일반적으로 클라우드 인프라에서 로드밸런서를 자동으로 프로비저닝 하는 기능을 제공한다.
그러나 쿠버네티스가 로드밸런서 서비스를 지원하지 않는 환경에서 실행 중인 경우 로드밸런서는 프로비저닝 되지 않지만 서비스는 여전히 노드포트 서비스처럼 작동한다.
로드밸런서런서 서비스는 노드포트 서비스의 확장이기 때문이다.
로드밸런서를 사용해 서비스를 생성해보자
kubia-svc-loadbalancer.yaml
apiVersion: v1
kind: Service
metadata:
name: kubia-loadbalancer
spec:
type: LoadBalancer # 이 유형의 서비스는 쿠버네티스 클러스터를 호스팅하는 인프라에서 로드밸런서를 얻을 수 있다.
ports:
- port: 80
targetPort: 8080
selector:
app: kubia
서비스 유형은 노드포트 대신 로드밸런서로 설정돼 있다. 특정 노드포트를 지정할 수 있지만 지정하지 않는다(쿠버네티스가 포트를 선택하게 한다)
서비스를 생성한 후 클라우드 인프라가 로드밸런서를 생성하고 IP 주소를 서비스 오브젝트에 쓰는 데 시간이 걸린다.
그것이 완료되면 로드밸런서 IP 주소가 서비스의 EXTENAL-IP 주소로 표시된다.
$ kubectl create -f kubia-svc-loadbalancer.yaml
service/kubia-loadbalancer created
$ kubectl get svc kubia-loadbalancer
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubia-loadbalancer LoadBalancer 10.36.11.98 34.83.221.88 80:32752/TCP 38s
$ curl http://34.83.221.88
You've hit kubia-xgws7
성공적으로 호출이된다. 노드포트 서비스와 달리 방화벽을 설정할 필요가 없었다.
인그레스가 필요한 이유는 로드밸런서 서비스는 자신의 공용 IP 주소를 가진 로드밸런서가 필요하지만, 인그레스는 한 IP 주소로 수십 개의 서비스에 접근이 가능하도록 지원해준다.
클라이언트가 HTTP 요청을 인그레스에 보낼 때, 요청한 호스트와 경로에 따라 요청을 전달할 서비스가 결정된다.
인그레스 오브젝트가 제공하는 기능을 살펴보기 전에 인그레스 리소스를 작동시키려면 클러스터에 인그레스 컨트롤러를 실행해야 한다.
쿠버네티스 환경마다 다른 컨트롤러 구현을 사용할 수 있지만 일부는 기본 컨트롤러를 전혀 제공하지 않는다.
구글 쿠버네티스 엔진은 구글 클라우드 플랫폼의 고유한 HTTP 로드밸런싱 기능을 사용해 인그레스 기능을 제공한다.
kubia-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: kubia
spec:
rules:
- host: kubia.example.com # 인그레스는 kubia.example.com 도메인 이름을 서비스에 매핑한다.
http:
paths:
- path: / # 모든 요청은 kubia-nodeport 서비스의 포트 80으로 전달된다.
pathType: Prefix
backend:
service:
name: kubia-nodeport
port:
number: 80
kubia.example.com으로 요청되는 인그레스 컨트롤러에 수신된 모든 HTTP 요청을 포트 80의 kubia-nodeport 서비스로 전송하도록 하는 인그레스 규칙을 정의했다.
http://kubia.example.com 서비스에 액세스하려면 도메인 이름이 인그레스 컨트롤러의 IP와 매핑되도록 해야한다.
$ kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
kubia <none> kubia.example.com 34.110.229.83 80 4m12s
인그레스 목록에서 IP를 찾을수 있다.
IP를 알고나면 kubia.example.com을 해당 IP로 확인하도록 DNS 서버를 구성하거나,
다음 줄을 /etc/hosts에 추가할 수 있다
34.110.229.83 kubia.example.com
이제 모든 것이 설정됐으므로 브라우저 또는 curl을 사용해 서비스에 액세스할 수 있다.
$ curl http://kubia.example.com
You've hit kubia-xgws7
인그레스 스펙을 자세히 보면 규칙과 경로가 모두 배열이므로 여러 항목을 가질 수 있다.
다음은 동일한 호스트의 다른 경로로 여러 서비스 매핑하는 방법이다
...
- host: kubia.example.com
http:
paths:
- path: /kubia # kubia.example.com/kubia 으로의 요청은 kubia 서비스로 라우팅된다.
pathType: Prefix
backend:
service:
name: kubia
port:
number: 80
- path: /bar
pathType: Prefix
backend:
service:
name: bar # kubia.example.com/bar 으로의 요청은 bar 서비스로 라우팅된다.
port:
number: 80
다음은 서로 다른 호스트로 서로 다른 서비스 매핑하는 방법이다.
spec:
rules:
- host: foo.example.com # foo.example.com 으로의 요청은 서비스 foo로 라우팅된다.
http:
paths:
- path: /
pathType: Prefix
backend:
service
name: foo
port:
number: 80
- host: bar.example.com # bar.example.com 으로의 요청은 서비스 bar로 라우팅된다.
http:
paths:
- path: /
pathType: Prefix
backend:
service
name: bar
port:
number: 80
예를 들어 파드가 웹 서버를 실행하는 경우 HTTP 트래픽만 허용하고 인그레스 컨트롤러가 TLS 와 관련된 모든 것을 처리하도록 할 수 있다.
컨트롤러가 그렇게 하려면 인증서와 개인 키를 인그레스에 첨부해야 한다. 이 두 개는 시크릿이라는 쿠버네티스 리소스에 저장하며 인그레스 매니페스트에서 참조한다.
먼저 개인 키와 인증서를 만들어야 한다.
$ openssl genrsa -out tls.key 2048
$ openssl req -new -x509 -key tls.key -out tls.cert -days 360 -subj /CN=kubia.example.com
그런 다음 두 파일로 시크릿을 만든다.
$ kubectl create secret tls tls-secret --cert=tls.cert --key=tls.key
secret/tls-secret created
개인 키와 인증서는 이제 tls-secret이라는 시크릿에 저장된다. 인그레스 오브젝트를 업데이트하려면 kubia.example.com에 대한 HTTPS 요청도 수락할 수 있다. 인그레스 매니페스트는 다음 예제와 같이 보일 것이다.
kubia-ingress-tls.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: kubia
spec: # 전체 TLS 구성이 이 속성 아래에 있다.
tls: # kubia.example.com 호스트 이름의 TLS 연결이 허용된다.
- hosts:
- kubia.example.com # 개인 키와 인증서는 이전에 작성한 tls-secret을 참조한다.
secretName: tls-secret
rules:
- host: kubia.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: kubia-nodeport
port:
number: 80
이제 HTTPS로 인그레스를 통해 서비스에 액세스할 수 있다.
$ curl -k -v https://kubia.example.com/kubia
명령어의 출력에는 애플리케이션 응답과 인그레스에 구성한 서버 인증서가 표시된다.
파드의 레이블이 서비스의 파드 셀렉터와 일치할 경우 파드가 서비스의 엔드포인트로 포함된다는 것을 배웠다.
적절한 레이블을 가진 새 파드가 만들어지자마자 서비스 일부가 돼 요청이 파드로 전달되기 시작한다.
하지만 만약 그 파드가 즉시 요청을 처리항 준비가 돼 있지 않다면 어떻게 해야 될까?
이 전 장에서 라이브니스 프로브와 불안전한 컨테이너를 자동으로 다시 시작해 애플리케이션의 상태를 원활히 유지하는 방법을 배웠다.
쿠버네티스에서는 라이브니스 프로브와 비슷하게 파드에 레디니스 프로브를 정의할 수 있다.
레디니스 프로브는 주기적으로 호출되며 특정 파드가 클라이언트 요청을 수신할 수 있는지를 결정한다. 컨테이너의 레디니스 프로브가 성공을 반환하면 컨테이너가 요청을 수락할 준비가 됐다는 신호다.
라이브니스 프로브와 마찬가지로 세 가지 유형의 레디니스 프로브가 있다.
kubectl edit 명령어로 기존 레플리케이션컨트롤러의 파드 템플릿에 프로브를 추가한다.
$ kubectl edit rc kubia
...
image: luksa/kubia # 해당 이미지 아래에 적용
readinessProbe: # 파드의 각 컨테이너에 레디니스 프로브가 정의될 수 있다.
exec:
command:
- ls
- /var/ready
...
레디니스 프로브는 컨테이너 내부에서 ls /var/ready 명령어를 주기적으로 수행한다.
ls 명령어는 파일이 존재하면 종료 코드 0을 반환하고 그렇지 않으면 0이 아닌 값을 반환한다.
파일이 있으면 레디니스 프로브가 성공하고 그렇지 않으면 실패한다
이렇게 정의한 이유는 문제의 파일을 생성하거나 제거해 그 결과를 바로 전환할 수 있기 때문이다.
기존 파드는 여전히 레디니스 프로브가 정의되어 있지않다. 파드를 삭제하면 레플리케이션컨트롤러가 다시 파드를 생성한다.
$ kubectl delete po --all
pod "kubia-fgrhp" deleted
pod "kubia-x7pq8" deleted
pod "kubia-xgws7" deleted
이제 새 파드는 레디니스 점검에 실패하고 각각에 /var/ready 파일을 만들 때까지 서비스의 엔드포인트에 포함되지 않는다.
$ kubectl get po
NAME READY STATUS RESTARTS AGE
kubia-hgsdk 0/1 Running 0 52s
kubia-q5k4d 0/1 Running 0 52s
kubia-q8ktv 0/1 Running 0 51s
아래의 명령으로 하나의 파드에 /var/ready 파일을 만들어 레디니스 프로브가 성공을 반환하도록 해보자.
$ kubectl exec kubia-hgsdk -- touch /var/ready
touch 명령어는 파일이 아직 없으면 파일을 만든다. 파드의 레디니스 프로브 명령이 이제 상태 코드 0으로 종료돼야 한다.
$ kubectl get po kubia-hgsdk
NAME READY STATUS RESTARTS AGE
kubia-hgsdk 1/1 Running 0 2m38s
레디니스 프로브는 기본으로 10초마다 주기적으로 프로브가 실행된다. 늦어도 10초 안에 파드는 준비 상태가 된다.
다음 명령을 사용하여 레디니스 프로브 파일이 설정된 해당 IP의 서비스의 유일한 엔드포인트로 조회하고 서비스를 호출해 본다.
$ kubectl get endpoints
NAME ENDPOINTS AGE
kubernetes 34.105.79.56:443 22h
kubia 10.32.1.13:8080 122m
kubia-loadbalancer 10.32.1.13:8080 97m
kubia-nodeport 10.32.1.13:8080 103m
$ curl http://10.32.1.13
You've hit kubia-hgsdk
$ curl http://10.32.1.13
You've hit kubia-hgsdk
$ curl http://10.32.1.13
You've hit kubia-hgsdk
여러번 호출해도 하나의 파드로 전달되는 것을 확인했다.
실제 환경에서 레디니스 프로브는 애플리케이션이 클라이언트 요청을 수신할 수 있는지 여부에 따라 성공 또는 실패를 반환해야 한다.
서비스에서 파드를 수동으로 제거하려면 수동으로 프로브의 스위치를 전환하는 대신 파드를 삭제하거나 파드 레이블을 변경해야 한다.
두 가지 강조 사항이 있다.
레디니스 프로브를 항상 정의한다.
파드에 레디니스 프로브를 추가하지 않으면 파드가 시작하는 즉시 서비스 엔드포인트가 된다.
애플리케이션이 수신 연결을 시작하는데 너무 오래 걸리는 경우 클라이언트의 서비스 요청은 여전히 시작 단계로 수신 연결을 수락할 준비가 되지 않은 상태에서 파드로 전달된다.
따라서 클라이언트는 Connection refused 유형의 에러를 보게된다.
레디니스 프로브에 파드의 종료 코드를 포함하지 않는다.
연결 오류가 발생한 클라이언트에서 파드가 종료할 때, 실행되는 애플리케이션은 종료 신호를 받자마자 연결 수락을 중단한다.
쿠버네티스는 파드를 삭제하자마자 모든 서비스에서 파드를 제거한다는 것을 기억하자.
지금까지 서비스의 파드에 클라이언트 연결을 허용하려고 서비스가 안정적인 IP 주소를 제공하는 방법을 살펴봤다.
서비스 스펙의 clusterIP 필드를 None으로 설정하면 쿠버네티스는 클라이언트가 서비스의 파드에 연결할 수 있는 클러스터 IP를 할당하지 않기 때문에 서비스가 헤드리스 상태가 된다.
헤드리스 서비스를 생성해 보자.
kubia-svc-headless.yaml
apiVersion: v1
kind: Service
metadata:
name: kubia-headless
spec:
clusterIP: None # 이 부분이 서비스를 헤드리스 서비스로 만든다.
ports:
- port: 80
targetPort: 8080
selector:
app: kubia
kubectl create로 서비스 생성 후 kubectl get과 kubectl describe로 서비스를 살펴볼 수 있다.
클러스터 IP가 없고 엔드포인트에 파드 셀렉터와 일치하는 파드가 포함돼 있음을 알 수 있다.
파드에 레디니스 프로브가 포함돼 있기 때문에 준비된 파드만 서비스의 엔드포인트로 조회된다.
계속하기 전에 이전 예제와 같이 파일을 만들어 두 개 이상의 파드가 준비돼 있는지 확인하자.
$ kubectl exec kubia-q5k4d -- touch /var/ready
파드가 준비되면 DNS 조회로 실제 파드 IP를 얻을 수 있는지 확인할 수 있다. 파드 내부에서 조회해야 한다.
클러스터에서 실행 중인 파드 내부에서 DNS 조회를 수행하기만 하면 된다. 필요한 바이너리가 포함된 이미지를 기반으로 새 파드를 실행하자.
DNS 관련 작업을 수행하려면 도커 허브의 nslookup 및 dig 바이너리를 모두 포함하는 tutum/dnsutils 컨테이너 이미지를 사용할 수 있다.
yaml이 아닌 명령어를 통해 파드를 만들어본다.
$ kubectl run dnsutils --image=tutum/dnsutils --command -- sleep infinity
새 서비스로 DNS 조회를 수행해보자.
$ kubectl exec dnsutils nslookup kubia-headless
Server: 10.36.0.10
Address: 10.36.0.10#53
Name: kubia-headless.default.svc.cluster.local
Address: 10.32.1.13
Name: kubia-headless.default.svc.cluster.local
Address: 10.32.1.14
FQDN에 대해 서로 다른 두 개의 IP를 반환한다. 바로 준비됐다고 보고된 파드 두 개의 IP다
아래는 kubia 서비스의 클러스터 IP 다.
$ kubectl exec dnsutils nslookup kubia
Server: 10.36.0.10
Address: 10.36.0.10#53
Name: kubia.default.svc.cluster.local
Address: 10.36.8.134
헤드리스 서비스는 일반 서비스와 다르게 보일 수 있지만 클라이언트 관점에서는 다르지 않다.
헤드리스 서비스를 사용하더라도 클라이언트는 일반 서비스와 마찬가지로 서비스의 DNS 이름에 연결해 파드에 연결할 수 있다. 그러나 헤드리스 서비스에서는 DNS가 파드의 IP를 반환하기 때문에 클라이언트는 서비스 프록시 대신 파드에 직접 연결한다.
마지막으로 서비스 IP 또는 FQDN으로 파드에 연결할 수 없는 이유를 파악하는데 다음과 같은 내용을 확인해 보자.