[Retry, k8s] 10. 서비스 API
카테고리: KUBERNETES
태그: kubernetes
[Retry, k8s] 10. 서비스 API
🔔 서비스 API 개요
클러스터상의 컨테이너에 대한 엔드포인트를 제공하거나 레이블과 일치하는 컨테이너의 디스커버리에 사용되는 리소스
📜 쿠버네티스 클러스터 네트워크와 서비스
쿠버네티스는 CNI 플러그인을 사용하여 파드 간 네트워킹을 구현하고, 서비스와 인그레스 리소스를 사용하여 파드와 외부의 통신을 추상화한다.
(1) 컨테이너 간의 통신
- 파드는 하나 이상의 컨테이너로 구성된 논리적 단위로, 같은 파드 내의 컨테이너는 동일한 IP 주소와 포트를 공유하고 localhost를 통해 통신할 수 있다.
(2) 파드 간의 통신 (CNI)
-
CNI 플러그인은 컨테이너 런타임에 의해 호출되어 파드가 생성/삭제될 때 네트워크 인터페이스를 추가/제거하고, 라우팅 규칙과 IP 주소 할당, 네트워크 정책을 적용하는 역할을 한다.
-
모든 파드는 고유한 IP 주소를 가지며, 다른 파드와 NAT 없이 통신할 수 있다.
-
모든 노드는 모든 파드와 NAT 없이 통신할 수 있다.
(3) 파드와 서비스 간 통신 (서비스, kube-proxy)
-
서비스 리소스는 일정한 IP 주소와 포트를 가지고, 동일한 기능을 제공하는 파드들의 집합을 추상화한 개념
-
서비스 리소스는 파드의 IP 주소가 변경되더라도 동일한 IP 주소와 포트를 유지하기 때문에 클라이언트가 파드의 위치를 신경쓰지 않고 통신할 수 있다.
-
kube-proxy는 각 노드에서 실행되는 구성요소로, 서비스 IP 주소와 포트에 대한 요청을 파드의 IP 주소와 포트로 프록시하는 역할을 수행한다.
(4) 외부와 서비스 간 통신 (인그레스)
-
인그레스 리소스는 외부에서 클러스터 내부의 서비스로 접근하기 위한 규칙들을 정의한 개념
-
인그레스 컨트롤러는 인그레스 리소스에 정의된 규칙들을 실제로 구현하는 구성요소로, 로드밸런서나 리버스 프록시 역할을 수행한다.
-
인그레스 컨트롤러가 파드에 접근하기 위해서는 CNI 플러그인과 호환되어야 한다.
📜 파드에 트래픽 로드밸런싱
서비스는 수신한 트래픽을 여러 파드에 로드밸런싱하는 기능을 제공한다.

cat <<EOF > sample-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: sample-deployment
spec:
replicas: 3
selector:
matchLabels:
app: sample-app
template:
metadata:
labels:
app: sample-app
spec:
containers:
- name: nginx-containers
image: amsy810/echo-nginx:v2.0
EOF
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
sample-deployment-79448fcf48-2kc5g 1/1 Running 0 111s
sample-deployment-79448fcf48-54g6x 1/1 Running 0 111s
sample-deployment-79448fcf48-6lzsb 1/1 Running 0 111s
# 특정 JSON Path 값만 출력
$ kubectl get pod sample-deployment-79448fcf48-2kc5g -o jsonpath='{.metadata.labels}'
{"app":"sample-app","pod-template-hash":"79448fcf48"}
# 지정한 레이블을 가진 파드 정보 중 특정 JSON Path를 컴럼으로 출력
$ kubectl get pod -l app=sample-app -o custom-columns="NAME:{metadata.name},IP:{status.podIP}"
NAME IP
sample-deployment-79448fcf48-2kc5g 10.38.0.3
sample-deployment-79448fcf48-54g6x 10.38.0.2
sample-deployment-79448fcf48-6lzsb 10.32.0.3
cat <<EOF > sample-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
name: sample-clusterip
spec:
type: ClusterIP
ports:
- name: "http-port"
protocol: "TCP"
port: 8080
targetPort: 80
selector:
app: sample-app
EOF
root@k8s-master:~/test# kubectl apply -f sample-clusterip.yaml
service/sample-clusterip created
$ kubectl get service sample-clusterip
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
sample-clusterip ClusterIP 10.96.249.28 <none> 8080/TCP 21s
$ kubectl describe service sample-clusterip
Name: sample-clusterip
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=sample-app
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.96.249.28
IPs: 10.96.249.28
Port: http-port 8080/TCP
TargetPort: 80/TCP
Endpoints: 10.32.0.3:80,10.38.0.2:80,10.38.0.3:80
Session Affinity: None
Events: <none>
# 일시적으로 파드를 시작하여 서비스 엔드포인트로 요청
$ kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s http://10.96.249.28:8080
Host=10.96.249.28 Path=/ From=sample-deployment-79448fcf48-2kc5g ClientIP=10.32.0.1 XFF=
pod "testpod" deleted
$ kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s http://10.96.249.28:8080
Host=10.96.249.28 Path=/ From=sample-deployment-79448fcf48-54g6x ClientIP=10.32.0.1 XFF=
pod "testpod" deleted
$ kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s http://10.96.249.28:8080
Host=10.96.249.28 Path=/ From=sample-deployment-79448fcf48-6lzsb ClientIP=10.32.0.1 XFF=
pod "testpod" deleted
📜 여러 포트 할당
하나의 서비스에 여러 포트를 할당할 수도 있다, 예를 들어 ClusterIP의 8080/TCP 포트로의 요청은 파드 80/TCP로 로드밸런싱하고, ClusterIP의 8443/TCP 포트로의 요청은 파드 443/TCP로 로드밸런싱한다.

cat <<EOF > sample-clusterip-multi.yaml
apiVersion: v1
kind: Service
metadata:
name: sample-clusterip-multi
spec:
type: ClusterIP
ports:
- name: "http-port"
protocol: "TCP"
port: 8080
targetPort: 80
- name: "https-port"
protocol: "TCP"
port: 8443
targetPort: 443
selector:
app: sample-app
EOF
📜 이름을 사용한 포트 참조
파드의 포트 정의에 이름을 지정해 놓으면 이름을 사용하여 참조할 수있다.
cat <<EOF > sample-named-port-pods.yaml
apiVersion: v1
kind: Pod
metadata:
name: sample-named-port-pod-80
labels:
app: sample-app
spec:
containers:
- name: nginx-container
image: amsy810/echo-nginx:v2.0
ports:
- name: http
containerPort: 80
---
apiVersion: v1
kind: Pod
metadata:
name: sample-named-port-pod-81
labels:
app: sample-app
spec:
containers:
- name: nginx-container
image: amsy810/echo-nginx:v2.0
env:
- name: NGINX_PORT
value: "81"
ports:
- name: http
containerPort: 81
EOF
cat <<EOF > sample-named-port-service.yaml
apiVersion: v1
kind: Service
metadata:
name: sample-named-port-service
spec:
type: ClusterIP
ports:
- name: "http-port"
protocol: "TCP"
port: 8080
targetPort: http
selector:
app: sample-app
EOF
# 서비스 목적지 엔드포인드 확인
$ kubectl describe service sample-named-port-service
Name: sample-named-port-service
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=sample-app
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.106.179.157
IPs: 10.106.179.157
Port: http-port 8080/TCP
TargetPort: http/TCP
Endpoints: 10.38.0.2:80,10.32.0.3:81
Session Affinity: None
Events: <none>
# 파드 IP 주소 확인
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
sample-named-port-pod-80 1/1 Running 0 88s 10.38.0.2 k8s-node01 <none> <none>
sample-named-port-pod-81 1/1 Running 0 88s 10.32.0.3 k8s-node02 <none> <none>
$ curl 10.106.179.157:8080
Host=10.106.179.157 Path=/ From=sample-named-port-pod-81 ClientIP=10.40.0.0 XFF=
$ curl 10.106.179.157:8080
Host=10.106.179.157 Path=/ From=sample-named-port-pod-80 ClientIP=10.40.0.0 XFF=
📜 클러스터 내부 DNS와 서비스 디스커버리
쿠버네티스는 서비스 디스커버리 기능을 서비스가 제공하고 있다.
-
쿠버네티스에서 서비스 디스커버리란 서비스에 속해 있는 파드를 보여주거나 서비스명에서 엔드포인트 정보를 반환하는 것을 말한다.
-
서비스 디스커버리 방법은 크게 세 가지가 있으며, 그 중 DNS를 사용한 서비스 디스커버리는 클러스터안에 있는 DNS 서버(클러스터 내부 DNS)에 자동으로 등록되는 서비스 엔드포인트 정보를 사용한다.
(1) 환경 변수를 사용한 서비스 디스커버리
-
파드 내부에서는 환경 변수에서도 같은 네임스페이스 서비스를 확인할 수 있다.(“-“이 포함된 서비스명은 “_“로 변경된 후 대문자로 변환됨)
-
그러나 파드가 생성된 서비스 생성이나 삭제에 따라 변경된 환경 변수가 먼저 기동한 파드 환경에는 자동으로 다시 등록되지 않기 때문에 예기치 못한 장애로 이어질 수 있기 때문에 먼저 생성한 파드를 다시 생성하자.
# 환경 변수에 등록된 서비스 정보 확인
$ kubectl exec -it sample-deployment-79448fcf48-2bgkv -- env | grep -i kubernetes
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
- 아래 예제와 같이 파드의 spec.enableServiceLinks를 false(기본값 true)로 설정하면 환경 변수 추가를 비활성화할 수 있다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: sample-deployment-servicelinks
spec:
replicas: 3
selector:
matchLabels:
app: sample-app
template:
metadata:
labels:
app: sample-app
spec:
enableServiceLinks: false
containers:
- name: nginx-container
image: amsy810/echo-nginx:v2.0
(2) DNS A 레코드를 사용한 서버스 디스커버리
-
다른 파드에서 서비스로 할당된 엔드포인트에 접속하려면 IP 주소를 사용하는 방법 외에도 자동 등록된 DNS 레코드를 사용할 수 있다.
-
할당된 IP 주소는 서비스를 생성할 때마다 변경되기에 IP 주소를 송신 측 컨테이너에 명시적으로 설정하면 변경될 때마다 변경해야 하기 떄문에 컨테이너를 변경 불가능(immutable) 상태로 유지할 수 없다.
-
그런 점에서 DNS명을 사용하면, 서비스 재생성에 따른 IP 주소 변경도 신경 쓸 필요가 없어진다.
$ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/sample-deployment-79448fcf48-2bgkv 1/1 Running 0 17m
pod/sample-deployment-79448fcf48-bxj9t 1/1 Running 0 17m
pod/sample-deployment-79448fcf48-qzwgv 1/1 Running 0 17m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/sample-clusterip ClusterIP 10.103.230.182 <none> 8080/TCP 78s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/sample-deployment 3/3 3 3 17m
NAME DESIRED CURRENT READY AGE
replicaset.apps/sample-deployment-79448fcf48 3 3 3 17m
# 일시적을 파드를 기동하여 컨테이너 내부에서 sample-clusterip:8080으로 HTTP 요청
$ kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s http://sample-clusterip:8080
Host=sample-clusterip Path=/ From=sample-deployment-79448fcf48-2bgkv ClientIP=10.32.0.1 XFF=
pod "testpod" deleted
$ kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s http://sample-clusterip:8080
Host=sample-clusterip Path=/ From=sample-deployment-79448fcf48-bxj9t ClientIP=10.32.0.1 XFF=
pod "testpod" deleted
- 이는 sample-clusterip의 이름 해석이 수행되고 해당 주소로 요청이 발송되도록 되어 있기 때문이며, 실제로 등록된 정식 FQDN는 “서비스명.네임스페이스명.svc.cluster.local”로 되어 있다.
# 일시적으로 파드를 기동하여 컨테이너 내부에서 sample-clusterip.default.svc.cluster.local의 이름 해석 확인
$ kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- dig sample-clusterip.default.svc.cluster.local
(생략)
;sample-clusterip.default.svc.cluster.local. IN A
;; ANSWER SECTION:
sample-clusterip.default.svc.cluster.local. 30 IN A 10.103.230.182
$ kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- dig -x 10.103.230.182
(생략)
;182.230.103.10.in-addr.arpa. IN PTR
;; ANSWER SECTION:
182.230.103.10.in-addr.arpa. 30 IN PTR sample-clusterip.default.svc.cluster.local.
(3) DNS SRV 레코드를 사용한 서비스 디스커버리
-
SRV 레코드는 포트명과 프로토콜을 사용하여 서비스를 제공하는 포트 번호를 포함한 엔드포인트를 DNS로 해석하는 구조다.
-
레코드 형식은 다음과 같다. (서비스의 포트명과 프로토콜에는 접두사에 “_“가 포함되므로 주의)
- “_서비스의 포트명._프로토콜.서비스명.네임스페이스명.svc.cluster.local
cat <<EOF > sample-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
name: sample-clusterip
spec:
type: ClusterIP
ports:
- name: "http-port"
protocol: "TCP"
port: 8080
targetPort: 80
selector:
app: sample-app
EOF
# 일시적으로 파드를 기동하여 SRV 레코드가 다른 파드에서 해석이 가능한지 확인
kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- dig _http-port.tcp.sample-clusterip.default.svc.cluster.local SRV
(생략)
;; QUESTION SECTION:
;_http-port.tcp.sample-clusterip.default.svc.cluster.local. IN SRV
;; ANSWER SECTION:
_http-port.tcp.sample-clusterip.default.svc.cluster.local. 30 IN SRV 0 100 8080 sample-clusterip.default.svc.cluster.local.
;; ADDITIONAL SECTION:
sample-clusterip.default.svc.cluster.local. 30 IN A 10.103.230.182
📜 클러스터 내부 DNS와 클러스터 외부 DNS
-
클러스터 내부 DNS에는 서비스 엔드포인트에 대한 레코드가 저장되어 있고, 클러스터 내부 DNS에 질의하여 서비스 디스커버리에서 사용된다.
-
물론 클러스터 내부 DNS에는 내부용 레코드만 등록되어 있으므로 이외의 레코드는 외부 DNS에 재귀 질의를 해야 한다.

📜 노드 로컬 DNS 캐시
-
클러스터 내부 DNS에 이름 해석을 보냈지만, 대규모 클러스터에서 성능 향상을 위해 각 노드의 로컬 DNS 캐시 서버를 포함하는 구조가 있다.
-
이 기능을 활성화한 환경에서 파드의 질의 대상은 같은 노드에 로컬 DNS 캐시 서버가 된다.

댓글 남기기