반응형
서비스 : 클라이언트가 파드를 검색하고 통신을 가능하게 함
- 파드는 일시적이다. 파드가 파드를 위한 공간을 확보하려고 노드에서 제거되거나, 누군가 파드 수를 줄이거나, 클러스터 노드의 장애로 언제든 다른 노드로 이동할 수 있다.
- 쿠버네티스는 노드에 파드를 스케줄링한 후 파드가 시작되기 바로 전에 파드의 IP 주소를 할당한다. 따라서 클라이언트는 서버인 파드의 IP 주소를 미리 알 수 없다.
- 수평 스케일링은 여러 파드가 동일한 서비스를 제공할 수 있음을 의미한다. 그리고 모든 파드를 단일 IP 주소로 액세스할 수 있어야 한다.
- 위와 같은 문제를 해결하려고 리소스 유형인 서비스를 제공한다.
5장에서 다루는 내용
- 단일 주소로 파드를 노출하는 서비스 리소스 만들기
- 클러스터 안에서 서비스 검색
- 외부 클라이언트에 서비스 노출
- 클러스터 내에서 외부 서비스 접속
- 파드가 서비스할 준비가 됐는지 제어하는 방법
- 서비스 문제 해결
5.1 서비스 소개
- 서비스는 동일한 서비스를 제공하는 파드 그룹에 지속적인 단일 접점을 만들려고 할 때 생성하는 리소스이다. 각 서비스는 서비스가 존재하는 동안 절대 바뀌지 않는 IP 주소와 포드가 있다.
- 클라이언트는 서비스를 제공하는 개별 파드의 위치를 알 필요 없으므로, 언제든지 클러스터 안에서 파드가 이동해도 상관이 없다.
- 웹 서버가 하나든 수백 개든 상관없이 외부 클라이언트는 프론트엔드 파드에 연결할 수 있어야 한다.
- 프론트엔드 파드는 백엔드 데이터베이스에 연결해야 한다. 데이터베이스는 파드 내에서 실행되므로 시간이 지남에 따라 클러스터 주위를 이동해 IP 주소가 변경될 수 있다.
- 예제에서는 프론트엔드 서비스, 백엔드 서비스를 만들어 클라이언트 -> 프론트엔드 서비스 -> 프론트엔드 파드 -> 백엔드 서비스 -> 백엔드 파드 이렇게 통신을 진행했다.
5.1.1 서비스 생성
- 서비스를 지원하는 파드가 한 개 혹은 그 이상일 수 있다. 서비스 연결을 서비스 뒷단의 모든 파드로 로드밸런싱된다. 그러나 정확히 어떤 파드가 서비스의 일부분인지 아닌지를 어떻게 정의하는가?
- 레이블 셀렉터를 이용해서 해당하는 파드를 서비스의 일부분이라 지정한다.
kubectl expose
를 사용해서 서비스를 생성할 수 있다.- expose 명령어는 레플리케이션컨트롤러에서 사용된 것과 동일한 파드 셀렉터를 사용해 서비스 리소스를 생성하고 모든 파드를 단일 IP 주소와 포트로 노출한다.
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
ports:
- port: 80 # 서비스가 사용할 포트
targetPort: 8080 # 서비스가 포워드할 컨테이너 포트
selector:
app: kubia
- YAML을 게시한 후 네임스페이스의 모든 서비스 리소스를 조회하고 서비스에 내부 클러스터 IP가 할당됐는지 확인할 수 있다.
$ kubectl get svc
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes 10.111.240.1 <none> 443/TCP 30d
kubia 10.111.249.153 <none> 80/TCP 6m
get svc
는 서비스를 확인하는 명령어이다.- kubia 서비스에 10.111.249.153 IP 주소가 할당됨을 알 수 있다.
- 클러스터 IP 이므로 클러스터 내부에서만 접근이 가능하다. 근데 서비스를 사용하는 이유 중 하나가 외부 클라이언트에게 접속하는 용도로 사용하기 때문에 외부로 노출하기도 해야한다.
- 동일한 명령을 몇번 실행하는 경우 동일한 클라이언트에서 요청을 보내는 것이더라도 서비스 프록시가 각 연결을 임의의 파드를 선택해 연결을 다시 전달하기 때문에 요청할 때마다 다른 파드가 선택된다.
- 반면 특정 클라이언트의 모든 요청을 매번 같은 파드로 리디렉션하려면 서비스의 세션어피니티 속성을 기본값 None 대신 ClientIP로 설정하면된다.
apiVersion: v1
kind: Service
spec:
sessionAffinity: ClientIP
...
- 쿠버네티스는 None과 ClientIP라는 두 가지 유형의 서비스 세션 어피니티만 지원한다.
- 쿠키 기반 세션 어피니티 옵션이 없지만 쿠버네티스 서비스가 HTTP 수준에서 작동하지 않는다는 것을 이해하면 크게 놀랍지 않다.
- 서비스는 TCP와 UDP 패킷을 처리하고 그들이 가지고 있는 페이로드는 신경 쓰지 않는다.
- 서비스는 단일 포트만 노출하지만 여러 포트를 지원할 수도 있다.
- 하나의 서비스를 사용해 멀티 포트 서비스를 사용하면 단일 클러스터 IP로 모든 서비스 포트가 노출된다.
- 여러 포트가 있는 서비스를 만들 때는 각 포트의 이름을 지정해야 한다.
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
ports:
- name: http
port: 80
targetPort: 8080
- name: https
port: 443
targetPort: 8443
selector:
app: kubia
- 포트 이름을 지정하는 이유는 나중에 서비스 스펙을 변경하지 않고도 포트 번호를 변경할 수 있다는 장점 때문이다.
- 파드는 현재 http라는 이름에 8080 포트를 사용하고 있지만 나중에 포트 80으로 변경하려면 포트 이름을 변경하지 않고 포트 번호를 변경하기만 하면 된다.
5.1.2 서비스 검색
- 서비스를 생성하면 IP 주소와 포트가 생긴다. 이 주소는 서비스가 유지되는 동안 변경되지 않는다. 이 서비스의 파드는 생성되기도 하고 사라지기도 하고 파드 IP가 변경되거나 파드 수는 늘어나거나 줄어들 수 있지만, 항상 서비스의 IP 주소로 액세스할 수 있어야 한다.
- 클라이언트 파드는 서비스의 IP와 포트를 알기 위해서는 아래와 같은 방법이 필요하다.
- 환경변수를 통해 서비스를 검색할 수 있다. 파드가 시작되면 쿠버네티스는 해당 시점에 존재하는 각 서비스를 가리키는 환경변수 세트를 초기화한다.
- 예제에서는 컨테이너 내부에서 env 명령어를 실행 해 환경변수를 조회 했다.
$ kubectl exec kubia-3inly env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin HOSTNAME=kubia-3inly KUBERNETES_SERVICE_HOST=10.111.240.1 KUBERNETES_SERVICE_PORT=443 ... KUBIA_SERVICE_HOST=10.111.249.153 # 서비스의 ClusterIP KUBIA_SERVICE_PORT=80 # 서비스가 제공되는 포트
- kubia 서비스와 관련된 변수 중에 서비스 IP 주소와 포트 정보를 갖는 KUBIA_SERVICE_HOST, KUBIA_SERVICE_PORT 두 개의 환경 변수가 있다.
- 위 두 개의 파드 내부 서비스 관련 환경변수로 서비스의 IP 주소와 포트를 찾을 수 있다.
- 허나, 대개 DNS의 도메인을 사용하는 것이 일반적이지 않은가?
- kube-system 네임스페이스를 확인 해보면 kube-dns라는 파드가 있다.
- kube-dns 파드는 DNS 서버를 실행하며 클러스터에서 실행 중인 다른 모든 파드는 자동으로 이를 사용하도록 구성된다. (쿠버네티스는 각 컨테이너의 /etc/resolv.conf 파일을 수정해 이를 수행한다.)
- 파드에서 실행 중인 프로세스에서 수행된 모든 DNS 쿼리는 시스템에서 실행 중인 모든 서비스를 알고 있는 쿠버네티스의 자체 DNS 서버로 처리된다.
- 각 서비스는 내부 DNS 서버에서 DNS 항목을 가져오고 서비스 이름을 알고 있는 클라이언트 파드는 환경변수 대신 FQDN으로 액세스할 수 있다.
- 위에서 프론트엔드 - 백엔드 예제를 보면 프론트엔드 파드는 FQDN으로 백엔드 데이터베이스 서비스에 연결할 수 있다.
backend-database.default.svc.cluster.local
backend-database는 서비스 이름이고 default는 서비스가 정의된 네임스페이스를 나타내며 svc.cluster.local 는 모든 클러스터의 로컬 서비스 이름에 사용되는 클러스터의 도메인 접미사다.- 도메인으로 접근은 가능하나 클라이언트는 여전히 서비스의 포트 번호를 알아야한다.
- 서비스에 연결하는 것은 간단하다. 프론트엔드 파드가 데이터베이스 파드와 동일한 네임스페이스에 있는 경우 svc.cluster.local 접미사와 네임스페이스는 생략할 수 있다.
- 서비스를 단순히 backend-database 라고 할 수 있다.
$ kubectl exec -it kubia-3inly bash
root@kubia-3inly:/#
root@kubia-3inly:/# curl http://kubia.default.svc.cluster.local
You’ve hit kubia-5asi2
root@kubia-3inly:/# curl http://kubia.default
You’ve hit kubia-3inly
root@kubia-3inly:/# curl http://kubia
You’ve hit kubia-8awf3
- 위와 같이 컨테이너 안에서 curl 명령어를 사용해 kubia 서비스에 접근할 수 있다.
5.2 클러스터 외부에 있는 서비스 연결
- 위에서는 서비스가 클러스터 내에 있는 파드로 연결을 전달하고자 했다. 하지만 외부 서비스를 노출하려는 경우가 있을 수 있다. 서비스가 외부 IP와 포트로 연결을 전달하는 것이 필요할 수 있다.
- 이 경우 서비스 로드밸런싱과 서비스 검색 모두 활용할 수 있다. 클러스터에서 실행중인 클라이언트 파드는 내부 서비스에 연결하는 것처럼 외부 서비스에 연결할 수 있다.
5.2.1 서비스 엔드포인트 소개
- 서비스는 파드에 직접 연결되지 않는다. 대신 엔드포인트 리소스가 그 사이에 있다.
kubectl describe
명령을 사용하면 엔드포인트를 확인할 수 있다.
$ kubectl describe svc kubia
Name: kubia
Namespace: default
Labels: <none>
Selector: app=kubia
Type: ClusterIP
IP: 10.111.249.153
Port: <unset> 80/TCP
Endpoints: 10.108.1.4:8080,10.108.2.5:8080,10.108.2.6:8080
Session Affinity: None
No events.
- 엔드포인트 리소스는 서비스로 노출되는 파드의 IP 주소와 포트 목록이다. 엔드포인트 리소스는 다른 쿠버네티스 리소스와 유사하므로
kubectl get
을 사용해 기본 정보를 표시할 수 있다.
$ kubectl get endpoints kubia
NAME ENDPOINTS AGE
kubia 10.108.1.4:8080,10.108.2.5:8080,10.108.2.6:8080 1h
- 파드 셀렉터는 서비스 스펙에 정의돼 있지만 들어오는 연결을 전달할 때 직접 사용하지는 않는다. 대신 셀렉터는 IP와 포트 목록을 작성하는 데 사용되며 엔드포인트 리소스에 저장된다.
- 클라이언트가 서비스에 연결하면 서비스 프록시는 이들 중 하나의 IP와 포트 쌍을 선택하고 들어온 연결을 대상 파드의 수신 대기 서버로 전달한다.
5.2.2 서비스 엔드포인트 수동 구성
- 서비스의 엔드포인트를 서비스와 분리하면 엔드포인트를 수동으로 구성하고 업데이트할 수 있다.
- 파드 셀렉터 없이 서비스를 만들면 쿠버네티스는 엔드포인트 리소스를 만들지 못한다. 서비스를 위한 엔드포인트 목록을 엔드포인트 리소스로 설정하는 것은 독자에게 달렸다.
- 수동으로 관리되는 엔드포인트를 사용해 서비스를 만들려면 서비스와 엔드포인트 리소스를 모두 만들어야 한다.
apiVersion: v1
kind: Service
metadata:
name: external-service # 서비스 이름은 엔드포인트 오브젝트 이름과 일치해야 한다.
spec: # 이 서비스에는 셀렉터가 정의돼 있지 않다.
ports:
- port: 80
- 포트 80으로 들어오는 연결을 허용하는 external-service라는 서비스를 정의하는 것이다. 서비스에 대한 파드 셀렉터를 정의하지 않았다.
- 엔드포인트는 별도의 리소스이며, 서비스 속성은 아니다. 셀렉터가 없는 서비스를 생성했기 때문에 엔드포인트 리소스가 자동으로 생성되지 않는다.
apiVersion: v1
kind: Endpoints
metadata:
name: external-service # 엔드포인트 오브젝트의 이름은 서비스 이름과 일치해야 한다.
subsets:
- addresses:
- ip: 11.11.11.11 # 서비스가 연결을 전달할 엔드포인트의 IP
- ip: 22.22.22.22
ports:
- port: 80
- 엔드포인트 오브젝트는 서비스와 이름이 같아야 하고 서비스를 제공하는 대상 IP 주소와 포트 목록을 가져야 한다. 서비스와 엔드포인트 리소스가 모두 서버에 게시되면 파드 셀렉터가 있는 일반 서비스처럼 서비스를 사용할 수 있다.
- 나중에 외부 서비스를 쿠버네티스 내에서 실행되는 파드로 마이그레이션하기로 결정한 경우 서비스에 셀렉터를 추가해 엔드포인트를 자동으로 관리할 수 있다.
- 서비스에서 셀렉터를 제거하면 쿠버네티스는 엔드포인트 업데이트를 멈춘다. 이는 서비스의 실제 구현이 변경되는 동안에도 서비스 IP 주소가 일정하게 유지될 수 있음을 의미한다.
5.2.3 외부 서비스를 위한 별칭 생성
- 서비스의 엔드포인트를 수동으로 구성해 외부 서비스를 노출하는 대신 좀 더 간단한 방법 FQDN으로 외부 서비스를 참조할 수 있다.
- 외부 서비스의 별칭으로 사용되는 서비스를 만들려면 type (유형) 필드를 ExternalName 으로 설정해 서비스 리소스를 만든다.
- api.somecompany.com에 공개 API가 있다고 가정하고 다음 예제에서는 이를 가리키는 서비스를 정의했다.
apiVersion: v1
kind: Service
metadata:
name: external-service
spec:
type: ExternalName
externalName: someapi.somecompany.com
ports:
- port: 80
- 서비스가 생성되면 파드는 서비스의 FQDN을 사용하는 대신 external-service.default.svc.cluster.local 도메인 이름으로 외부 서비스에 연결할 수 있다.
- 즉, 위와 같은 서비스를 이용해서 someapi.somecompany.com에 대한 CNAME을 external-service.default.svc.cluster.local 으로 지정할 수 있다는 이야기
- 이렇게 하면 서비스를 사용하는 파드에서 실제 서비스 이름과 위치가 숨겨져 나중에 externalName 속성을 변경하거나 유형을 다시 ClusterIP로 변경하고 서비스 스펙을 만들어 서비스 스펙을 수정하면 나중에 다른 서비스를 가리킬 수 있다.
- 저렇게 external-service.default.svc.cluster.local로 외부 서비스에 연결하게 되면 실제 서비스에 대한 정보를 모르더라도 externalName을 통해 목적지의 도메인을 변경하거나 ClusterIP를 지정해 다른 서비스로 통신할 수 있다.
- ExternalName 서비스는 DNS 레벨에서만 구현된다. 서비스에 연결하는 클라이언트는 서비스 프록시를 완전히 무시하고 외부 서비스에 직접 연결된다.
5.3 외부 클라이언트에 서비스 노출
- 지금까지는 클러스터 내부에서 파드가 서비스를 사용하는 방법을 알아봤다. 웹 서버와 같은 특정 서비스를 외부에 노출해 외부 클라이언트가 액세스할 수 있게 하려면 어떻게 하는가?
- 외부에서 서비스를 액세스할 수 있는 몇 가지 방법이 있다.
- 노드포트로 서비스 유형 설정 : 노드포트 서비스의 경우 각 클러스터 노드는 노드 자체에서 포트를 열고 해당 포트로 수신된 트래픽을 서비스로 전달한다. 이 서비스는 내부 클러스터 IP와 포트로 액세스할 수 있을 뿐만 아니라 모든 노드의 전용 포트로도 액세스할 수 있다.
- 로드밸런서로 서비스 유형 설정 : 쿠버네티스가 실행 중인 클라우드 인프라에서 프로비저닝된 전용 로드밸런서로 서비스에 액세스할 수 있다. 로드밸런서는 트래픽을 모든 노드의 노드포트로 전달한다. 클라이언트는 로드밸런서의 IP로 서비스에 액세스한다.
- 인그레스 리소스 만들기 : 단일 IP 주소로 여러 서비스를 노출하는 인그레스는 HTTP 레벨레서 작동하므로 4계층 서비스보다 더 많은 기능을 제공할 수 있다.
5.3.1 노드포트 서비스 사용
- 노드포트 서비스를 만들면 쿠버네티스는 모든 노드에 특정 포트를 할당하고 서비스를 구성하는 파드로 들어오는 연결을 전달한다.
- 일반 서비스와 유사하지만 서비스의 내부 ClusterIP 뿐만 아니라 모든 노드의 IP와 할당된 노드포트로 서비스에 액세스할 수 있다.
apiVersion: v1
kind: Service
metadata:
name: kubia-nodeport
spec:
type: NodePort
ports:
- port: 80 # 서비스 내부 클러스터 IP의 포트
targetPort: 8080 # 서비스 대상의 파드 포트
nodePort: 30123 # 각 클러스터 노드의 30123으로 서비스에 액세스할 수 있다.
selector:
app: kubia
- 유형을 NodePort로 설정하고 모든 노드에 바인딩돼야 하는 노드 포트를 지정해준다.
- 노드포트를 지정하지 않게되면 쿠버네티스가 임의의 포트를 선택해준다.
$ kubectl get svc kubia-nodeport
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubia-nodeport 10.111.254.223 <nodes> 80:30123/TCP 2m
- 확인 해보면 노드 포트 서비스가 잘 생성된 것을 볼 수 있다. 만약 EXTERNAL-IP:30123으로 접속하게 되면 CLUSTER-IP:80으로 접속하게되는 것과 동일하다.
5.3.2 외부 로드밸런서로 서비스 노출
- 클라우드 공급자에서 실행되는 쿠버네티스 클러스터는 일반적으로 클라우드 인프라에서 로드밸런서를 자동으로 프로비저닝하는 기능을 제공한다.
- 로드밸런서는 공개적으로 액세스 가능한 고유한 ip 주소를 가지며 모든 연결을 서비스로 전달한다. 따라서 로드밸런서의 IP 주소로 서비스에 액세스할 수 있다.
- 쿠버네티스가 로드밸런서 서비스를 지원하지 않는 환경에서 실행 중인 경우 로드밸런서는 프로비저닝되지 않지만 서비스는 여전히 노드포트 서비스처럼 작동한다. 로드밸런서 서비스는 노드포트 서비스의 확장이기 때문이다.
apiVersion: v1
kind: Service
metadata:
name: kubia-loadbalancer
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
selector:
app: kubia
- 로드밸런서 유형 서비스는 추가 인프라 제공 로드밸런서가 있는 노드포트 서비스다. kubectl describe를 사용해 서비스에 대한 추가 정보를 보면 서비스에 노드포트가 선택 됐음을 알 수 있다.
5.3.3 외부 연결의 특성 이해
- 외부 클라이언트가 노드포트로 서비스에 접속할 경우 임의로 선택된 파드가 연결을 수신한 동일한 노드에서 실행 중일 수도 있고, 그렇지 않을 수도 있다. 파드에 도달하려면 추가적인 네트워크 홉이 필요할 수 있으며 이것이 항상 바람직한 것은 아니다.
- 외부 연결을 수신한 노드에서 실행 중인 파드로만 외부 트래픽을 전달하도록 서비스를 구성해 이 추가 홉을 방지할 수 있다. 서비스의 스펙 섹션의 externalTrafficPolicy 필드를 설정하면된다.
spec:
externalTrafficPolicy: Local
...
- 서비스 정의에 externalTrafficPolicy 설정이 포함돼 있고 서비스의 노드포트로 외부 연결이 열린 경우 서비스 프록시는 로컬에 실행 중인 파드를 선택한다.
- 로컬 파드가 존재하지 않으면 연결이 중단된다. 따라서 로드밸런서는 그러한 파드가 하나 이상 있는 노드에만 연결을 전달하도록 해야 한다.
- 일반적으로 모든 연결은 모든 파드에 균등하게 분산되지만 이 어노테이션을 사용하게 되면 균등하게 분산되지 않는다.
- 로컬 외부 트래픽 정책(externalTrafficPolicy)을 사용하는 서비스는 파드 간 부하가 고르지 않을 수도 있다는 뜻.
- 일반적으로 클러스터 내의 클라이언트가 서비스로 연결할 때 서비스의 파드는 클라이언트의 IP 주소를 얻을 수 있다. 그러나 노드포트로 연결을 수신하면 패킷에서 소스 네트워크 주소 변환이 수행되므로 패킷의 소스 IP가 변경된다.
- 파드는 실제 클라이언트의 IP를 볼 수 없다. 이는 클라이언트의 IP를 알아야하는 일부 애플리케이션에서 문제가 될 수 있다. 예를 들어 웹 서버의 경우 액세스 로그에 브라우저의 IP를 표시하지 못한다는 것을 의미한다.
- 이전 절에서 설명한 Local External Traffic Policy는 연결을 수신하는 노드와 대상 파드를 호스팅하는 노드 사이에 추가 홉이 없기 때문에 클라이언트 IP 보존에 영향을 미친다. (SNAT가 수행되지 않는다.)
5.4 인그레스 리소스로 서비스 외부 노출
- 외부에서 쿠버네티스에 접속할 수 있는 또 다른 방법은 왜 필요한가? (이미 위에서 설명했는데도 불구하고)
- 한 가지 중요한 이유는 로드밸런서 서비스는 자신의 공용 IP 주소를 가진 로드밸런서가 필요하지만, 인그레스는 한 IP 주소로 수십 개의 서비스에 접근이 가능하도록 지원해준다.
- 클라이언트가 HTTP 요청을 인그레스에 보낼 때, 요청한 호스트와 경로에 따라 요청을 전달할 서비스가 결정된다.
- 위와 같이 인그레스는 네트워크 스택의 애플리케이션 계층(HTTP)에서 작동하며 서비스가 할 수 없는 쿠키 기반 세션 어피니티 등과 같은 기능을 제공할 수 있다.
- 인그레스 리소스를 작동시키려면 클러스터에 인그레스 컨트롤러를 실행해야 한다. 쿠버네티스 환경마다 다른 컨트롤러 구현을 사용할 수 있지만 일부는 기본 컨트롤러를 전혀 제공하지 않는다.
5.4.1 인그레스 리소스 생성
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: kubia
spec:
rules:
- host: kubia.example.com
http:
paths:
- path: /
backend:
serviceName: kubia-nodeport
servicePort: 80
- Host kubia.example.com으로 요청되는 인그레스 컨트롤러에 수신된 모든 HTTP 요청을 포트 80의 kubia-nodeport 서비스로 전송하도록 하는 인그레스 규칙을 정의했다.
5.4.2 인그레스 서비스 액세스
- http://kubia.example.com 서비스에 액세스하려면 도메인 이름이 인그레스 컨트롤러의 IP와 매핑되도록 확인해야 한다.
$ kubectl get ingresses
NAME HOSTS ADDRESS PORTS AGE
kubia kubia.example.com 192.168.99.100 80 29m
192.168.99.100 kubia.example.com
- IP를 알고나면 /etc/hosts에 위 항목을 추가해 매핑해준다.
$ curl http://kubia.example.com
You've hit kubia-ke823
- 인그레스에 성공적으로 접근했다.
- 위와 같은 과정이 된 이유를 더 살펴보면, 클라이언트는 먼저 kubia.example.com의 DNS 조회를 수행했으며 DNS 서버가 인그레스 컨트롤러의 IP를 반환한다. 그런 다음 HTTP 요청을 인그레스 컨트롤러로 전송하고 host 헤더에서 kubia.example.com을 지정한다.
- 컨트롤러는 해당 헤더에서 클라이언트가 액세스하려는 서비스를 결정하고 서비스와 관련된 엔드포인트 오브젝트로 파드 IP를 조회한 다음 클라이언트 요청을 파드에 전달한다.
- 인그레스 컨트롤러는 요청을 서비스로 전달하지 않고, 파드를 선택하는 데만 사용한다. 모두는 아니지만 대부분의 컨트롤러는 이와 같이 동작한다.
5.4.3 하나의 인그레스로 여러 서비스 노출
- 인그레스 스펙을 자세히 보면 규칙과 경로가 모두 배열이므로 여러 항목을 가질 수 있다.
- 인그레스는 여러 호스트와 경로를 여러 서비스(backend, serviceName)에 매핑할 수 있다.
- host: kubia.example.com
http:
paths:
- path: /kubia
backend:
serviceName: kubia
servicePort: 80
- path: /bar
backend:
serviceName: bar
servicePort: 80
- 이 경우 요청은 URL의 경로에 따라 두 개의 다른 서비스로 전송된다. 따라서 클라이언트는 단일 IP 주소로 두 개의 서비스에 도달할 수 있다.
spec:
rules:
- host: foo.example.com
http:
paths:
- path: /
backend:
serviceName: foo
servicePort: 80
- host: bar.example.com
http:
paths:
- path: /
backend:
serviceName: bar
servicePort: 80
- 컨트롤러가 수신한 요청은 요청의 호스트헤더에 따라 서비스 foo 또는 bar로 전달된다. DNS는 두 도메인 이름을 모두 인그레스 컨트롤러의 IP 주소로 지정해야 한다.
5.4.4 TLS 트래픽을 처리하도록 인그레스 구성
- 클라이언트가 인그레스 컨트롤러에 대한 TLS 연결을 하면 컨트롤러는 TLS 연결을 종료한다. (TLS Termination) (Offloading)
- 클라이언트와 컨트롤러 간의 통신은 암호화되지만 컨트롤러와 백엔드 파드 간의 통신은 암호화되지 않는다.
- 컨트롤러가 이렇게 하여면 인증서와 개인 키를 인그레스에 첨부해야 한다. 이 두 개는 시크릿이라는 쿠버네티스 리소스에 저장하며 인그레스 매니페스트에서 참조한다.
$ 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
5.5 파드가 연결을 수락할 준비가 됐을 때 신호 보내기
- 서비스와 인그레스에서 살펴봐야 할 점이 파드가 준비가 되지 않았을 때 요청을 보내면 어떻게 되는지에 대한 것이다.
- 파드가 구성에 시간이 오래 걸리거나 데이터를 로드하는 데 시간이 오래 걸릴 수도 있고, 첫 번째 사용자 요청이 너무 오래 걸리거나 사용자 경험에 영향을 미치는 것을 방지하고자 준비하는 절차를 수행해야 할 수도 있다.
- 파드가 완전히 준비될 때까지 기동 중인 파드에 요청을 전달하지 않는 것이 좋다.
5.5.1 레디니스 프로브
- 앞서 라이브니스 프로브와 불완전한 컨테이너를 자동으로 다시 시작해 애플리케이션을 상태를 원활히 유지하는 법을 배웠다.
- 라이브니스 프로브와 비슷하게 readiness probe 레디니스 프로브를 정의할 수 있다.
- 레디니스 프로브는 주기적으로 호출되며 특정 파드가 클라이언트의 요청을 받을 수 있는지 결정한다.
- being ready 라는 준비가 됐다는 표시는 분명히 각 컨테이너마다 다르다. 쿠버네티스는 컨테이너에서 실행되는 애플리케이션이 간단한 GET / 요청에 응답하는지 또는 특정 URL 경로를 호출할 수 있는지 확인하거나 필요에 따라 애플리케이션이 준비됐는지 확인하기 위해 전체적인 항목을 검사하기도 한다.
- 레디니스 프로브에는 세 가지 유형이 있다.
- 프로세스를 실행하는 Exec 프로브 : 컨테이너의 상태를 프로세스의 종료 상태 코드로 결정한다.
- HTTP GET 프로브 : HTTP GET 요청을 컨테이너로 보내고 응답의 HTTP 상태 코드를 보고 컨테이너가 준비됐는지 여부를 결정한다.
- TCP 소켓 프로브 : 컨테이너의 지정된 TCP 포트로 연결을 열고, 소켓이 연결되면 컨테이너가 준비된 것으로 간주한다.
- 컨테이너가 시작될 때 쿠버네티스는 첫 번째 레디니스 점검을 수행하기 전에 구성 가능한 시간이 경과하기를 기다리도록 구성할 수 있다.
- 그런 다음 주기적으로 프로브를 호출하고 레디니스 프로브의 결과에 따라 작동한다.
- 파드가 준비되지 않았다고 하면 서비스에서 제거된다. 파드가 다시 준비되면 서비스에 다시 추가된다.
- 라이브니스 프로브와 달리 컨테이너가 준비 상태 점검에 실패하더라도 컨테이너가 종료되거나 다시 시작되지 않는다.
- 이는 라이브니스 프로브와 레디니스 프로브의 가장 중요한 차이점이다.
- 라이브니스 프로브는 상태가 좋지 않은 컨테이너를 제거하고 새롭고 건강한 컨테이너로 교체해 파드의 상태를 정상으로 유지하는 반면, 레디니스 프로브는 요청을 처리할 준비가 된 파드의 컨테이너만 요청을 수신하도록 한다.
- 이것은 컨테이너를 시작할 때 주로 필요하지만 작동한 후에도 유용하다.
- 하위 그림에서 보듯이 파드의 레디니스 프로브가 실패하면 파드는 엔드포인트 오브젝트에서 제거된다. 서비스로 연결하는 클라이언트의 요청은 파드로 전달되지 않는다.
- 파드 그룹(애플리케이션 서버를 실행하는 파드)이 다른 파드(백엔드 데이터베이스)에서 제공하는 서비스에 의존한다고 가정해보자.
- 프론트엔드 파드 중 하나에 연결 문제가 발생해 더 이상 데이터베이스에 연결할 수 없는 경우, 해당 시점에 파드가 해당 요청을 처리할 준비가 되지 않았다는 신호를 레디니스 프로브가 쿠버네티스에게 알리는 것이 현명할 수 있다.
- 레디니스 프로브를 사용하면 클라이언트가 정상 상태인 파드하고만 통신하고 시스템에 문제가 있다는 것을 알아채지 못하도록 한다.
5.5.2 파드에 레디니스 프로브 추가
apiVersion: v1
kind: ReplicationController
...
spec:
...
template:
...
spec:
containers:
- name: kubia
image: luksa/kubia
readinessProbe:
exec:
command:
- ls
- /var/ready
...
- 레디니스 프로브는 컨테이너 내부에서 ls /var/ready 명령어를 주기적으로 수행하고 ls 명령어는 파일이 존재하면 종료 코드 0을 반환하고 그렇지 않으면 0이 아닌 값을 반환한다.
5.5.3 실제 환경에서 레디니스 프로브가 수행해야 하는 기능
- 실제 환경에서 레디니스 프로브는 애플리케이션이 클라이언트 요청을 수신할 수 있는지 여부에 따라 성공 또는 실패를 반환해야 한다.
- 레디니스 프로브에서 강조해야 할 두 가지 사항이 있다.
- 레디니스 프로브를 항상 정의하라 : 먼저 파드에 레디니스 프로브를 추가하지 않으면 파드가 시작하는 즉시 서비스 엔드포인트가 된다. 애플리케이션이 수신 연결을 시작하는 데 너무 오래 걸리는 경우 클라이언트의 서비스 요청은 여전히 시작 단계로 수신 연결을 수락할 준비가 되지 않은 상태에서 파드로 전달된다. 따라서 클라이언트는 "Connection refused" 유형의 에러를 보게 된다.
- 레디니스 프로브에 파드의 종료 코드를 포함하지 마라 : 파드의 라이프사이클 마지막 단계(파드 종료)에서 고려해야 하며 연결 오류가 발생한 클라이언트와 관련된 내용이다. 파드가 종료할 때, 실행되는 애플리케이션은 종료 신호를 받자마자 연결 수락을 중단한다. 그렇기 때문에 종료 절차가 시작되는 즉시 레디니스 프로브가 실행하도록 만들어 파드가 모든 서비스에서 확실하게 제거돼야 한다고 생각할 수 있다. 그러나 그건 필요하지 않다. 쿠버네티스는 파드를 삭제하자마자 모든 서비스에서 파드를 제거하기 때문이다.
5.6 헤드리스 서비스로 개별 파드 찾기
- 지금까지 서비스의 파드에 클라이언트의 연결을 허용하려고 서비스가 안정적인 IP 주소를 제공하는 방법을 살펴봤다. 서비스의 연결은 임의의 파드로 전달된다.
- 그러나 클라이언트가 모든 파드에 연결해야 하는 경우 어떻게 해야 할까? 파드가 다른 파드에 각각 연결해야 하는 경우 어떻게 해야할까? 서비스로 연결하는 것은 확실한 방법이 아니다.
- 클라이언트가 모든 파드에 연결하려면 각 파드의 IP를 알아야 한다. 한 가지 옵션은 클라이언트가 쿠버네티스 API 서버를 호출해 파드와 IP 주소 목록을 가져오도록 하는 것이다.
- 하지만 애플리케이션을 쿠버네티스와 무관하게 유지하려고 노력해야 하기 때문에 항상 API 서버를 사용하는 것은 바람직하지 않다.
- 다행히 쿠버네티스는 클라이언트가 DNS 조회로 파드 IP를 찾을 수 있도록 한다. 일반적으로 서비스에 대한 DNS 조회를 수행하면 DNS 서버는 하나의 IP를 반환한다.
- 그러나 쿠버네티스 서비스에 클러스터 IP가 필요하지 않다면(clusterIP 필드를 None으로 설정해 이를 수행하면) DNS 서버는 하나의 서비스 IP대신 파드 IP들을 반환한다.
- DNS 서버는 하나의 DNS A 레코드를 반환하는 대신 서비스에 대한 여러 개의 A 레코드를 반환한다. 각 레코드는 해당 시점에 서비스를 지원하는 개별 파드의 IP를 가리킨다.
- 따라서 클라이언트는 간단한 DNS A 레코드 조회를 수행하고 서비스에 포함된 모든 파드의 IP를 얻을 수 있다.
5.6.1 헤드리스 서비스 생성
- 서비스 스펙의 clusterIP 필드를 None으로 설정하면 쿠버네티스는 클라이언트가 서비스의 파드에 연결할 수 있는 클러스터 IP를 할당하지 않기 때문에 서비스가 헤드리스 상태가 된다.
apiVersion: v1
kind: Service
metadata:
name: kubia-headless
spec:
clusterIP: None # 이 부분이 서비스를 헤드리스 서비스로 만든다.
ports:
- port: 80
targetPort: 8080
selector:
app: kubia
- 클러스터 IP가 없고 엔드포인트에 파드 셀렉터와 일치하는 파드가 포함돼 있음을 알 수 있다. 파드에 레디니스 프로브가 포함돼 있기 때문에 준비된 파드만 서비스의 엔드포인트로 조회된다.
5.6.2 DNS로 파드 찾기
- 파드가 준비되면 DNS 조회로 실제 파드 IP를 얻을 수 있는지 확일할 수 있다.
$ kubectl exec dnsutils nslookup kubia-headless
...
Name: kubia-headless.default.svc.cluster.local
Address: 10.108.1.4
Name: kubia-headless.default.svc.cluster.local
Address: 10.108.2.5
$ kubectl exec dnsutils nslookup kubia
...
Name: kubia.default.svc.cluster.local
Address: 10.111.249.153
- kubia-headless 는 보는 바와 같이 두개의 파드 아이피를 반환하고, kubia는 서비스의 clusterIP를 반환하는 모습이다.
- 클라이언트의 관점에서는 헤드리스 서비스를 사용하더라도 마찬가지로 서비스의 DNS 이름에 연결해 파드에 연결할 수 있다. 그러나 헤드리스 서비스에서는 DNS가 파드의 IP를 반환하기 때문에 클라이언트는 서비스 프록시 대신 파드에 직접 연결한다.
5.6.3 모든 파드 검색 - 준비되지 않은 파드도 포함
- 준비된 파드만 서비스의 엔드포인트가 되는 것을 보았다. 그러나 때로는 서비스 레이블 셀렉터에 매칭되는 모든 파드를 찾는 서비스 검색 메커니즘을 원할 때가 있다.(준비되지 않은 것 포함)
- 다행히 쿠버네티스 API 서버를 쿼리할 필요 없이 DNS 조회 메커니즘을 사용해 준비되지 않은 파드도 찾을 수 있다. 쿠버네티스가 파드의 레디니스 상태에 관계없이 모든 파드를 서비스에 추가되게 하려면 서비스에 다음 어노테이션을 추가해야 한다.
kind: Service
metadata:
annotations:
service.alpha.kubernetes.io/tolerate-unready-endpoints: "true"
위에서는 service.alpha.kubernetes.io/tolerate-unready-endpoints 어노테이션을 이용했지만, publishNotReadyAddresses 서비스 스펙 필드를 지원해 위 어노테이션을 대체한다.
5.7 서비스 문제 해결
- 서비스는 쿠버네티스의 중요한 개념이며 많은 개발자가 좌절하는 이유다. 많은 개발자들이 서비스 IP 또는 FQDN으로 파드에 연결할 수 없는 이유를 파악하는 데 많은 시간을 허비한다.
- 서비스로 파드에 액세스할 수 없는 경우 다음과 같은 내용을 확인한 후에 다시 시작해보자
- 먼저 외부가 아닌 클러스터 내에서 서비스의 클러스터 IP에 연결되는지 확인한다.
- 서비스에 액세스할 수 있는지 확인하려고 서비스 IP로 핑을 할 필요 없다.(서비스의 클러스터 IP는 가상 IP이므로 핑되지 않는다.)
- 레디니스 프로브를 정의했다면 성공했는지 확인하라. 그렇지 않으면 파드는 서비스에 포함되지 않는다.
- FQDN이나 그 일부로 서비스에 액세스하려고 하는데 작동하지 않는 경우, FQDN 대신 클러스터 IP를 사용해 액세스할 수 있는지 확인한다.
- 대상 포트가 아닌 서비스로 노출된 포트에 연결하고 있는지 확인한다.
- 파드 IP에 직접 연결해 파드가 올바른 포트에 연결돼 있는지 확인한다.
- 파드 IP로 애플리케이션에 액세스할 수 없는 경우 애플리케이션이 로컬호스트에만 바인딩하고 있는지 확인한다.
느낀 점
흠... 역시 서비스는 조금 어려운 것 같다. 중간에 이해되지 않는 말들이 조금 있었던 것 같다. 그리고 5.7에서 서비스 문제 해결하는 방법이 있는데서비스에 액세스할 수 있는지 확인하려고 서비스 IP로 핑을 할 필요 없다
이 부분이 잘 이해가 가지 않는다.
정리
서비스 사용 개요
- 서비스를 왜 사용함? -> 파드는 일시적이고 가변에는 IP를 갖고 있고 수평 파드 스케일링을 사용하면 하나의 인입점이 필요하기 때문에
- 서비스는 서비스가 존재하는 동안 IP와 포트가 절대 바뀌지 않는다.
- 클라이언트는 파드가 어느곳에 위치하는 지 알 필요 없이 서비스만 호출하면 동일한 애플리케이션을 제공 받을 수 있음
- 클라이언트 -> 프론트엔드 서비스 -> 프론트엔드 파드 -> 백엔드 서비스 -> 백엔드 파드 이런 흐름으로 접근이 된다.
- 서비스는 레이블 셀렉터를 이용해서 서비스 하위의 파드들을 정의한다.
- 또한
kubectl expose
명령어를 사용해서 서비스를 생성할 수 있다. - 서비스는
kubectl get svc
또는kubectl get service
로 확인 가능 - get svc로 확인한 컬럼 중 CLUSTER-IP와 EXTERNAL-IP 가 있는데
- CLUSTER-IP 는 클러스터 내부에서만 접근이 가능한 IP이다.
- EXTERNAL-IP 는 클러스터 외부 클라이언트에게 접속하는 용도이다.
- 쿠버네티스는 None과 ClientIP라는 두 가지 유형의 서비스 세션 어피니티만 지원한다.
- ClientIP로 지정하게 되면 세션을 고정해서 A 사용자는 A 파드에만 접속하게 끔 고정할 수 있다. (Sticky session과 동일한 기능인듯 함)
- 서비스는 단일 포트만 노출하는 것이 아니라 여러 포트를 지원할 수도 있다.
- 이 경우 포트의 이름을 지정해주어야 한다. 왜? 나중에 설정 변경을 하게 되면 이름과 포트 둘다 변경하지 않고 한 쪽만 변경해주면 되기 때문에 편하다.
서비스 검색 방법
- 서비스의 IP 주소와 포트를 알기 위해서 서비스를 검색할 수 있는 방법이 있다.
- 파드 내부의 환경변수를 관리해 서비스 IP, 포트 정보를 가질 수 있다.
- kube-system 네임 스페이스 내부의 kube-dns 파드로 질의
- FQDN 을 이용해 서비스에 질의
파드 내부의 환경 변수
- 파드는 시작할 때 서비스를 바라보고 있는 환경변수 세트를 초기화한다.
- 아래 명령어로 특정 파드에게 env 를 확인하도록 요청하면
KUBIA_SERVICE_HOST
KUBIA_SERVICE_PORT
환경 변수가 있다. (예시) - 두 개의 파드 내부 서비스 관련 환경변수로 서비스의 IP 주소와 포트를 찾을 수 있다.
kube-system 네임 스페이스 내부 kube-dns
- kube-dns 파드는 DNS 서버를 실행하고, 클러스터에서 실행 중인 다른 모든 파드는 자동으로 이를 사용하도록 구성된다.
- 컨테이너 내부의 /etc/resolv.conf 파일을 수정해서 이를 수행한다.
- 해당 파드로 모든 DNS 쿼리를 보내 쿠버네티스 자체 DNS 서버가 처리한다.
- 편하게 생각하면 쿠버네티스 내부에 있는 네임 서버라고 생각하면 된다.
FQDN을 이용한 연결
- 각 서비스는 내부 DNS 서버에서 DNS 항목을 가져오고 서비스 이름을 알고 있는 클라이언트 파드는 환경변수 대신 FQDN으로 액세스할 수 있다.
- 구조는
backend-database.default.svc.cluster.local
이와 같다.- backend-database는 서비스의 이름
- default는 네임스페이스 이름
- svc.cluster.local은 클러스터의 도메인 접미사
- 도메인으로 접근은 되나 클라이언트는 여전히 서비스의 포트 번호를 알아야 한다.
- 또한 서비스에 연결하기 위해서는 아래와 같다.
- 동일 네임스페이스에서 통신하는 경우 backend-database 로 접근 가능
- 외부 네임스페이스에서 통신하는 경우 backend-database.default.svc.cluster.local 로 접근
- 클러스터 외부에서 접근하고자 할 때는 어떨까? 서비스를 외부로 노출해야 한다.
- 서비스 엔드포인트 리소스를 이용해서 노출할 수 있다.
서비스 엔드포인트 사용
- 위 경우 Endpoints 에 서비스로 노출되는 파드의 IP 주소와 포트 목록이 있다.
- 이는 파드 셀렉터를 통해서 엔드포인트 리소스에 저장된다.
- 엔드포인트는 그저 IP와 포트 목록을 작성해 저장하고, 이를 통해 클라이언트가 서비스에 연결되면 서비스 프록시가 목록을 보고 파드로 전달해준다.
- 파드 셀렉터 없이 서비스를 만들게 되면 쿠버네티스는 엔드포인트 리소스를 만들지 못하며, 수동으로 엔드포인트를 사용해 서비스를 만들려면 서비스와 엔드포인트 리소스를 모두 수동으로 만들어야 한다.
- 수동으로 서비스와 엔드포인트를 만든 경우 엔드포인트 오브젝트의 metadata.name은 서비스의 metadata.name과 동일해야한다.
- 위와 같이 구성하면 파드 셀렉터가 있는 일반 서비스처럼 서비스를 사용할 수 있다.
ExternalName
- 클러스터 내부에 있는 서비스가 아니라 외부 서비스와 FQDN으로 통신하기 위해서는 ExternalName을 이용하면 된다.
- 서비스 스펙에서 type: ExternalName 으로 설정해 서비스 리소스를 만든다.
- 서비스를 위와 같이 생성하면 공개 API 주소인 someapi.somecompany.com 으로 접근하기 위해서 external-service.default.svc.cluster.local로 연결할 수 있다.
- 이렇게 구성하면 서비스를 사용하는 파드에서 실제 서비스 이름과 위치가 숨겨지고, spec.type을 ClusterIP로 변경하거나 spec.externalName을 수정하면 다른 서비스를 가리킬 수도 있다.
- ExternalName 서비스는 DNS 레벨에서만 구현된다. 서비스에 연결하는 클라이언트는 서비스 프록시를 완전히 무시하고 외부 서비스에 직접 연결된다.
- google-service.default.svc.cluster.local로 연결 가능하다.
외부에 서비스를 노출하기
- 클러스터 외부에서 서비스를 액세스할 수 있는 방법이 몇 가지 있다.
- 노드포트로 각 클러스터 노드 자체에서 포트를 열고 해당 포트로 수신된 트래픽을 서비스로 전달한다. 이 서비스는 내부 클러스터 IP와 포트로 액세스할 수 있을 뿐만 아니라 모든 노드의 전용 포트로도 액세스할 수 있다.
- 로드 밸런서로 클라우드 인프라에서 프로비저닝된 전용 로드밸런서로 서비스에 액세스 할 수 있다. 클라이언트는 로드밸런서의 IP로 서비스에 액세스한다.
- 인그레스로 단일 IP 주소로 여러 서비스를 노출할 수 있다. HTTP 레벨에서 작동해 4계층 서비스보다 더 많은 기능을 제공할 수 있다.
노드포트 사용하기
- 노드포트는 모든 노드에 특정 포트를 할당하고 서비스를 구성하는 파드로 들어오는 연결을 전달한다.
- 일반 서비스와 유사하지만 서비스의 내부 ClusterIP 뿐만 아니라 모든 노드의 IP와 할당된 노드포트로 서비스에 액세스할 수 있다.
- 외부에서 EXTERNAL-IP:30123 으로 접근하는 것과 CLUSTER-IP:80 으로 접근하는 것이 동일하다.
클라우드 로드밸런서 사용하기
- 로드밸런서는 공개적으로 액세스 가능한 고유한 IP 주소를 가지며 모든 연결을 서비스로 전달한다. 따라서 로드밸런서의 IP로 서비스에 액세스할 수 있다.
- 쿠버네티스가 로드밸런서 서비스를 지원하지 않는 환경에서라면 로드밸런서는 프로비저닝되지 않지만 서비스는 여전히 노드포트 서비스처럼 작동한다. 로드밸런서는 노드포트 서비스의 확장이기 때문이다.
인그레스 사용하기
- 로드밸런서 서비스는 자신의 공용 IP 주소를 가진 로드 밸런서가 필요하지만, 인그레스는 한 IP 주소로 수십 개의 서비스에 접근이 가능하도록 지원해준다.
- 클라이언트가 HTTP 요청을 인그레스에 보낼 때 요청한 호스트와 경로에 따라 요청을 전달할 서비스가 결정된다.
- 인그레스는 OSI의 애플리케이션 계층에서 작동하며 서비스가 할 수 없는 쿠키 기반 세션 어피니티 등과 같은 기능 제공
- 인그레스 리소스를 작동시키려면 클러스터에 인그레스 컨트롤러를 실행해야 한다.
- 인그레스를 통해 접속을 하는 방법은 아래와 같다.
- Host kubia.example.com으로 요청되는 인그레스 컨트롤러에 수신된 모든 HTTP 요청을 포트 80의 kubia-nodeport 서비스로 전송하도록 하는 인그레스 규칙을 정의할 수 있다.
- http://kubia.example.com 서비스에 액세스하려면 도메인 이름이 인그레스 컨트롤러의 IP와 매핑이 되어야 한다.
- 인그레스 컨트롤러와 매핑하기 위해서 /etc/hosts 파일을 수정하게 되면 인그레스에 성공적으로 접근이 된다.
- 컨트롤러는 해당 헤더에서 클라이언트가 액세스하려는 서비스를 결정하고 서비스와 관련된 엔드포인트 오브젝트로 파드 IP를 조회한 다음 클라이언트 요청을 파드에 전달한다.
- 인그레스는 여러 항목을 가질 수 있다. 그 말인 즉슨 path를 여러개로 분리해 하나의 인그레스를 구성해 단일 IP 주소로 여러 서비스로 보낼 수 있다는 말이다.
- 클라이언트가 인그레스 컨트롤러에 대한 TLS 연결을 하면 컨트롤러는 TLS 연결을 종료한다. (TLS Termination/offloading)
- 클라이언트와 컨트롤러 간의 통신은 암호화되지만 컨트롤러와 백엔드 파드 간의 통신은 암호화되지 않는다.
- 컨트롤러가 이렇게 하려면 인증서와 개인 키를 인그레스에 첨부해야 한다. 이 두개는 시크릿이라는 쿠버네티스 리소스에 저장하며 인그레스 매니페스트에서 참조한다.
레디니스 프로브 : 파드가 연결 수락의 준비가 됐을 때 신호 보내기
- 파드가 구성에 시간이 오래 걸리거나 데이터를 로드하는 데 시간이 오래 걸릴 수 있으며, 첫 번째 사용자 요청이 너무 오래 걸리거나 사용자 경험에 영향을 미치는 것을 방지하고자 준비하는 절차를 수행해야 할 수도 있다.
- 파드가 완전히 준비될 때까지 기동 중인 파드에 요청을 전달하지 않는 것이 좋다.
- 라이브니스 프로브는 불완전한 컨테이너를 자동으로 다시 시작해서 애플리케이션을 정상 상태로 유지하는 것이다.
- 레디니스 프로브는 주기적으로 호출되며 특정 파드가 클라이언트의 요청을 받을 수 있는지 결정한다.
- 준비가 됐다는 표시는 컨테이너마다 다르다. 레디니스 프로브에는 세 가지 유형이 있기 때문이다.
- 프로세스를 실행하는 Exec 프로브 : 컨테이너의 상태를 프로세스의 종료 상태 코드로 결정한다.
- HTTP GET 프로브 : HTTP GET 요청을 컨테이너로 보내고 응답의 HTTP 상태 코드를 보고 컨테이너가 준비 됐는지 여부를 결정
- TCP 소켓 프로브 : 컨테이너의 지정된 TCP 포트로 연결을 열고, 소켓이 연결되면 컨테이너가 준비된 것으로 간주
- 또한 컨테이너가 시작될 때 첫 번째 레디니스 점검을 수행하기 전에 구성 가능한 시간이 경과하기를 기다리도록 구성할 수 있다.
- readinessProbe.initialDelaySeconds 로 설정 가능
- 라이브니스 프로브와 달리 상태 점검에 실패하더라도 컨테이너가 종료되거나 다시 시작되지 않는다.
- 라이브니스 프로브와 레디니스 프로브와의 가장 큰 차이점
- 라이브니스 프로브는 상태가 좋지 않은 파드는 바로 교체해 버리는데 레디니스 프로브는 문제가 생긴 파드는 아예 통신을 끊어 버린다.
- 요청을 처리할 준비가 된 파드의 컨테이너만 요청을 수신하도록 한다.
- 클라이언트로 하여금 정상 상태인 파드하고만 통신 할 수 있도록하고 시스템에 문제가 있다는 것을 알아채지 못하도록 한다.
- 레디니스 프로브는 강조해야 할 두 가지 사항이 있다.
- 레디니스 프로브를 항상 정의하라 : 파드에 레디니스 프로브를 추가하지 않으면 파드가 시작하는 즉시 서비스 엔드포인트가 된다. 파드가 연결을 수락할 주비가 되지 않았어도 트래픽이 파드로 전달되고 클라이언트는 "Connection Refused" 유형의 에러를 보게 된다.
- 레디니스 프로브에 파드의 종료 코드를 포함하지 마라 : 파드가 종료될 때 레디니스 프로브가 즉시 실행 되도록 만들어 파드를 모든 서비스에서 제거해야 한다고 생각할 수 있다. 근데 불필요하다 왜냐면 쿠버네티스는 파드를 삭제하자마자 모든 서비스에서 파드를 제거하기 때문
헤드리스 서비스로 개별 파드 찾기
- 클라이언트가 모든 파드에 연결하고자 한다면 어떻게 해야할까?
- 쿠버네티스는 클라이언트가 DNS 조회로 파드 IP를 찾을 수 있도록 한다.
- 서비스 스펙에서
ClusterIP: None
으로 설정이 되어 있는 경우 쿠버네티스는 클라이언트가 서비스의 파드에 연결할 수 있는 클러스터 IP를 할당하지 않기 때문에 서비스가 헤드리스 상태가 된다. - 만약 헤드리스 상태인 서비스에 nslookup 명령어를 실행하게 되면 서비스의 셀렉터로 묶여 있는 파드들의 모든 IP를 얻을 수 있다.
- 헤드리스 서비스는 클라이언트 입장에서는 일반 서비스와 마찬가지로 DNS 이름에 연결해 파드에 연결 가능하다. 그런데 헤드리스 서비스에서는 DNS가 파드의 IP를 반환 해 클라이언트는 서비스 프록시 대신 파드에 직접 연결한다.
모든 파드 검색 - 준비되지 않은 파드도 포함
- 서비스 레이블 셀렉터에 매칭되는 모든 파드를 찾는 서비스 검색 매커니즘을 원할 때가 있다. (준비되지 않은 것도 포함해서)
- 쿠버네티스 API 서버를 쿼리할 필요 없이 DNS 조회 매커니즘을 사용해 준비되지 않은 파드도 찾을 수 있다.
- 쿠버네티스가 파드의 레디니스 상태에 관계없이 모든 파드를 서비스에 추가되게 하려면 서비스에 다음 어노테이션을 추가해야 한다.
service.alpha.kubernetes.io/tolerate-unready-endpoints: "true"
- 지금은 publishNotReadyAddresses 서비스 스펙 필드를 지원해 위 어노테이션을 대체할 수 있다.
서비스 문제 해결
- 서비스로 파드에 액세스할 수 없는 경우 가장 먼저 확인해야할 사항
- 클러스터 내에서 서비스의 ClusterIP에 접속 되는 지 확인
- 서비스의 ClusterIP는 ping이 되지 않으니 핑을 할 필요 없다. (가상 IP이므로 핑이 안된다) -> 이거는 이해가 잘 안갔었는데 그냥 ping을 지원을 안한다는 이야기
- 레디니스 프로브를 정의했다면 성공 했는지 확인해야 함. 성공하지 않았다면 파드는 서비스에 포함되지 않는다.
- FQDN이나 그 일부로 서비스에 액세스하려고 했는데 작동안하면, FQDN 대신 클러스터 IP를 사용해 액세스할 수 있는 지 확인
- 대상 포트가 아닌 서비스로 노출된 포트에 연결하고 있는지 확인
- 파드 IP에 직접 연결해 파드가 올바른 포트에 연결돼 있는지 확인
- 파드 IP로 애플리케이션에 액세스할 수 없는 경우 애플리케이션이 로컬 호스트에만 바인딩하고 있는지 확인
참고 자료
쿠버네티스 인 액션
반응형
'책, 강의' 카테고리의 다른 글
[쿠버네티스 인 액션] 4장 - 레플리케이션과 그 밖의 컨트롤러: 관리되는 파드 배포 (1) | 2023.11.02 |
---|---|
[쿠버네티스 인 액션] 3장 - 쿠버네티스에서 컨테이너 실행 (1) | 2023.10.30 |
[쿠버네티스 인 액션] 2장 - 도커와 쿠버네티스 첫걸음 (1) | 2023.10.29 |
[쿠버네티스 인 액션] 1장 - 쿠버네티스 소개 (1) | 2023.10.29 |