[kubernetes] Service & Ingress

728x90

Service

공식 문서: https://kubernetes.io/ko/docs/concepts/services-networking/service/

파드 집합에서 실행중인 애플리케이션을 네트워크 서비스로 노출하는 추상화 방법

쿠버네티스를 사용하면 익숙하지 않은 서비스 디스커버리 메커니즘을 사용하기 위해 애플리케이션을 수정할 필요가 없다.

쿠버네티스는 파드에게 고유한 IP 주소와 파드 집합에 대한 단일 DNS 명을 부여하고, 그것들 간에 로드-밸런스를 수행할 수 있다.

서비스는 selector 를 사용해서 pod 의 label 을 찾고 뒷단으로 연결시키기 때문에 만약 다른 pod 에서 만든 컨테이너의 label 이 셀렉터에서 찾는 label 과 같다면 서비스의 뒷단에 연결해버린다.

그래서, label 과 selector 를 잘 설정해줘야 한다.

kubectl explain svc.spec
  • clusteIP: 서비스에 IP 주소를 지정한다. 서비스의 갯수가 많아지면 IP 주소를 관리하기 힘들어지기 때문에 굳이 사용하지 않는다.
  • ports: 서비스의 포트를 지정한다. 즉, 클라이언트가 서비스에 접속할 포트 번호이다.
  • selector: 서비스가 연결하기 위한 파드의 label 을 지정한다.
  • type: 서비스의 타입을 지정한다. ExternalName, ClusterIP, NodePort, LoadBalancer 가 있다.
    • NodePort, LoadBalancer: 클러스터 외부에 노출하기 위한 것
    • ClusterIP: 클러스터 내부에서 사용하기 위한 것
kubectl explain svc.spec.ports
  • name: 포트의 이름
  • port: 서비스를 외부에 노출시킬 포트를 지정
  • protocol: 프로토콜을 지정한다. (TCP, UDP, STCP)
  • targetPort: 서비스와 연결하려는 파드의 포트를 지정한다.

서비스의 이름과 똑같은 엔드포인트가 만들어지고, 엔드포인트가 실제 파드의 정보를 갖고 있다. 연결된 파드가 삭제되거나 종료될 경우 자동으로 파드를 만들어서 엔드포인트에 연결시켜준다.

myweb-rs.yml

metadata:
  name: myweb-rs-set
spec:
  replicas: 3
  selector:
    matchExpressions:
      - key: app
        operator: In
        values:
          - web
      - key: env
        operator: Exists
  template:
    metadata:
      labels:
        app: web
        env: dev
    spec:
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb
          ports:
            - containerPort: 8080
              protocol: TCP

myweb-svc.yml

apiVersion: v1
kind: Service
metadata:
  name: myweb-svc
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080
kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool

bash-5.1# curl 10.101.197.28
Hello World!
myweb-rs-set-89q25

bash-5.1# cat /etc/resolv.conf
nameserver 10.96.0.10  # <- 쿠버네티스 CoreDNS 주소. 즉, 쿠버네티스 안에서 자기들끼리 통신이 가능하다.
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

bash-5.1# curl myweb-svc
Hello World!

sessionAffinity

kubectl explain svc.spec

sessionAffinity: 스티키 세션. 즉, 세션을 고정시키는 것

[~/k8s/svc]$ cat myweb-svc-ses.yml
apiVersion: v1
kind: Service
metadata:
  name: myweb-svc-ses
spec:
  type: ClusterIP
  sessionAffinity: ClientIP
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080
[~/k8s/svc]$ kubectl create -f myweb-svc-ses.yml -f myweb-rs.yml
service/myweb-svc-ses created
[~/k8s/svc]$ kubectl get svc,ep,rs,pod
NAME                    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/kubernetes      ClusterIP   10.96.0.1      <none>        443/TCP   35h
service/myweb-svc-ses   ClusterIP   10.99.16.135   <none>        80/TCP    11s

NAME                      ENDPOINTS                                         AGE
endpoints/kubernetes      192.168.49.2:8443                                 35h
endpoints/myweb-svc-ses   10.244.0.3:8080,10.244.1.2:8080,10.244.2.2:8080   11s

NAME                           DESIRED   CURRENT   READY   AGE
replicaset.apps/myweb-rs-set   3         3         3       28s

NAME                     READY   STATUS    RESTARTS   AGE
pod/myweb-rs-set-69bgq   1/1     Running   0          28s
pod/myweb-rs-set-8h7ht   1/1     Running   0          28s
pod/myweb-rs-set-n6xzw   1/1     Running   0          28s
[~/k8s/svc]$ kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
If you don't see a command prompt, try pressing enter.
/ # curl myweb-svc-ses
Hello World!
myweb-rs-set-n6xzw
/ # curl myweb-svc-ses
Hello World!
myweb-rs-set-n6xzw
/ # curl myweb-svc-ses
Hello World!

Named Port

[~/k8s/svc]$ cat myweb-rs-named.yml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myweb-rs-naemd
spec:
  replicas: 3
  selector:
    matchExpressions:
      - key: app
        operator: In
        values:
          - web
      - key: env
        operator: Exists
  template:
    metadata:
      labels:
        app: web
        env: dev
    spec:
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb
          ports:
            - containerPort: 8080
              protocol: TCP
              name: web8080 # containerPort/protocol 의 alias 를 뜻함. 즉, 8080/TCP 라고 생각하면 된다.

[~/k8s/svc]$ cat myweb-svc-named.yml
apiVersion: v1
kind: Service
metadata:
  name: myweb-svc-named
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: web8080 # containerPort/protocol 의 alias 를 가져온다.
[~/k8s/svc]$ kubectl get svc,ep,rs,pod
NAME                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
service/kubernetes        ClusterIP   10.96.0.1        <none>        443/TCP   35h
service/myweb-svc-named   ClusterIP   10.110.107.247   <none>        80/TCP    4s

NAME                        ENDPOINTS                                         AGE
endpoints/kubernetes        192.168.49.2:8443                                 35h
endpoints/myweb-svc-named   10.244.0.4:8080,10.244.1.3:8080,10.244.2.5:8080   4s

NAME                             DESIRED   CURRENT   READY   AGE
replicaset.apps/myweb-rs-naemd   3         3         3       4s

NAME                       READY   STATUS    RESTARTS   AGE
pod/myweb-rs-naemd-4qs2k   1/1     Running   0          4s
pod/myweb-rs-naemd-ctnrn   1/1     Running   0          4s
pod/myweb-rs-naemd-xfgfj   1/1     Running   0          4s

name 필드로 containerPort/protocol 의 alias 를 사용하여 쓸 수 있다.

multi-port

[~/k8s/svc]$ cat myweb-svc-multi.yml
apiVersion: v1
kind: Service
metadata:
  name: myweb-svc-multi
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080
      name: http
    - port: 443
      targetPort: 8443
      name: https
[~/k8s/svc]$ cat myweb-rs-multi.yml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myweb-rs-multi
spec:
  replicas: 3
  selector:
    matchExpressions:
      - key: app
        operator: In
        values:
          - web
      - key: env
        operator: Exists
  template:
    metadata:
      labels:
        app: web
        env: dev
    spec:
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb
          ports:
            - containerPort: 8080
              protocol: TCP
            - containerPort: 8443
              protocol: TCP
[~/k8s/svc]$ kubectl get svc,ep,rs,pod
NAME                      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/kubernetes        ClusterIP   10.96.0.1       <none>        443/TCP          35h
service/myweb-svc-multi   ClusterIP   10.104.41.191   <none>        80/TCP,443/TCP   5s

NAME                        ENDPOINTS                                                     AGE
endpoints/kubernetes        192.168.49.2:8443                                             35h
endpoints/myweb-svc-multi   10.244.0.5:8443,10.244.1.4:8443,10.244.2.6:8443 + 3 more...   5s

NAME                             DESIRED   CURRENT   READY   AGE
replicaset.apps/myweb-rs-multi   3         3         3       46s

NAME                       READY   STATUS    RESTARTS   AGE
pod/myweb-rs-multi-489wk   1/1     Running   0          46s
pod/myweb-rs-multi-fbvhm   1/1     Running   0          46s
pod/myweb-rs-multi-jqcl2   1/1     Running   0          46s

multi-port 는 말 그대로 포트를 여러개 열어놓고 해당하는 포트로 들어올 경우 연결된 포트로 포워딩 시켜준다.

여기서는 80 으로 들어올 경우 8080 으로 포워딩하고 443 으로 들어올 경우 8443 으로 포워딩 해준다.

Service Discovery - DNS

SVC 로드 밸런서를 찾을 수 있는 DNS 이름이 필요로 하다.

환경 변수를 이용한 Service Discovery

모든 파드는 실행 시 현재 시점의 서비스 목록을 환경 변수로 제공한다.

[~/k8s/svc]$ kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
/ # env
MYWEB_SVC_PORT_80_TCP_ADDR=10.108.235.162
PWD=/
KUBERNETES_SERVICE_HOST=10.96.0.1
MYWEB_SVC_SERVICE_PORT=80
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_SERVICE_PORT_HTTPS=443
MYWEB_SVC_PORT=tcp://10.108.235.162:80
MYWEB_SVC_SERVICE_HOST=10.108.235.162
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
TERM=xterm
MYWEB_SVC_PORT_80_TCP=tcp://10.108.235.162:80
HOME=/root
SHLVL=1
HOSTNAME=nettool
MYWEB_SVC_PORT_80_TCP_PROTO=tcp
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.96.0.1:443
MYWEB_SVC_PORT_80_TCP_PORT=80

DNS 를 이용한 Service Discovery

host 명령어는 DNS 서버에 쿼리하는 명령어이다.

/ # host myweb-svc
myweb-svc.default.svc.cluster.local has address 10.108.235.162

myweb-svc.default.svc.cluster.local 는 FQDN 으로 완전한 주소 이름이다.

쿠버네티스 내부에서 FQDN 은 [서비스_이름].[Name_Space].[오브젝트_타입].[Domain] 으로 구성된다.

cat /etc/resolv.conf
nameserver 169.254.25.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

여기서 nameserver 는 coredns 주소가 아닌 nodelocal DNS(DNS Cache Server) 주소이다.

search default.svc.cluster.local svc.cluster.local cluster.local 라고 되어있는 데 myweb-svc 를 질의를 던질 때 myweb-svc 뒤에 search 에 있는 것을 하나씩 붙여서 DNS 에 쿼리를 날리는 것이다.

즉, myweb-svc 로 쿼리를 날리면 myweb-svc.default.svc.cluster.local 라는 FQDN 을 만들어서 쿼리를 날린다.

만약 myweb-svc.default 로 쿼리를 날리면 search 순서에 따라 myweb-svc.default.default.svc.cluster.local 으로 쿼리를 날린다. 당연하게도 쿼리가 실패하게 되고 그 다음 search 를 붙여서 쿼리를 날린다. 즉, svc.cluster.local 를 붙여서 myweb-svc.default.svc.cluster.local 를 쿼리로 날리는 것이다.

/ # host myweb-svc
myweb-svc.default.svc.cluster.local has address 10.108.235.162
/ # host myweb-svc.default
myweb-svc.default.svc.cluster.local has address 10.108.235.162
/ # host myweb-svc.default.svc
myweb-svc.default.svc.cluster.local has address 10.108.235.162
/ # host myweb-svc.default.svc.cluster.local
myweb-svc.default.svc.cluster.local has address 10.108.235.162

만약 Name Space 가 dev 일 경우

공식 문서: https://kubernetes-docsy-staging.netlify.app/ko/docs/concepts/services-networking/dns-pod-service/, https://kubernetes.io/ko/docs/tasks/administer-cluster/dns-custom-nameservers/

Name Space 가 default 가 아닐 경우에는 dns 서버에 쿼리를 날려도 호스트 IP 를 가져올 수 없다. 왜냐하면 etc/resolv.conf 의 search 에 세팅된 Name Space 가 dev 로 설정되어 있기 때문이다.

그래서 Name Space 가 default 인 녀석을 추가해줘야 한다.

kubectl create ns dev
kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm --namespace=dev --overrides='{"spec": { "dnsConfig": { "searches": ["default.svc.cluster.local"] } }}'

run 할 때 --overrides 옵션을 사용하여 default.svc.cluster.local 을 추가해준다.

--overrides='{"spec": { "dnsConfig": { "searches": ["default.svc.cluster.local"] } }}'

nodelocal DNS(DNS Cache Server, DNS Forwarder)

공식 문서: https://kubernetes.io/docs/tasks/administer-cluster/nodelocaldns/

Cache Hit: 캐시 서버에 캐싱된 값을 돌려준다.

Cache miss: 캐시 서버에 캐싱되지 않아 실제 서버에 쿼리를 보내서 값을 가져온다.

vagrant@node-1 ~ » sudo ss -tnlp
State  Recv-Q Send-Q     Local Address:Port    Peer Address:Port Process
LISTEN 0      4096       169.254.25.10:9254         0.0.0.0:*     users:(("node-cache",pid=1978,fd=7))
LISTEN 0      4096           127.0.0.1:10248        0.0.0.0:*     users:(("kubelet",pid=772,fd=21))
....

169.254.25.10:9254 을 보면 node-cache 로 되어있는데 이게 바로 nodelocal DNS 이다.

가상의 DNS 로, pod 가 어떤 노드에 배치되든 간에 pod 는 coredns SVC(kube-system NS) 로 바로 쿼리를 날리지 않고 nodelocal DNS 에 쿼리를 날린다.

Cache DNS Server 에 실질적인 레코드는 없고 pod 로 부터 쿼리를 받으면 해당 쿼리를 대신해서 DNS Server 에 보내고 DNS Answer 가 오면 해당 값을 캐싱한다.

vagrant@node-1 ~ » kubectl get pod -A                                                            
NAMESPACE     NAME                                      READY   STATUS    RESTARTS        AGE
....
kube-system   nodelocaldns-42hw5                        1/1     Running   50 (179m ago)   3d21h
kube-system   nodelocaldns-4rdnt                        1/1     Running   32 (178m ago)   3d21h
kube-system   nodelocaldns-kdz54                        1/1     Running   3 (177m ago)    24h

실행 중인 각각의 노드 마다 하나의 nodelocaldns 가 세팅된 것을 확인할 수 있다.

nodelocal DNS 캐시 사용 시

Pod --dns → DNS Cache Server → coredns SVC(kube-system NS) → coredns Pod

nodelocal DNS 캐시 사용 X

Pod --dns → corends SVC(kube-system NS) → coredns Pod

Service - nodePort

NodePort의 범위: 30000-32767

  • ClusterIP: 클러스터 내부에서 사용하는 LB
  • Load Balancer: 클러스터 외부에서 접근하는 LB
  • NodePort: 클러스터 외부에서 접근하는 포트

Service - LoadBalancer

Metallb - Addon

metallb 공식 문서: https://metallb.universe.tf/

kubespray metallb 설치 공식문서: https://kubespray.io/#/docs/metallb?id=metallb

https://cyuu.tistory.com/170

https://velog.io/@youwins/MetalLB

bareMetal 환경에서 LoadBalancer type의 서비스를 만들기 위해 사용한다.

왜 metalLB을 사용하느냐 클라우드 없이 LoadBalancer type의 서비스를 구현할 때 사용한다.

  • Pod는 휘발성을 가지므로 고정된 엔드포인트를 자체적으로 가지기 힘들다. Kubernetes의 Pod는 상황에 따라 소멸-재생성을 반복하게 된다. 문제는 이 때마다 지정되는 IP가 바뀌어 엔드포인트로 호출이 어렵다. 또한 여러 Pod에 같은 애플리케이션을 운용할 때 어느 Pod로 트래픽을 보낼지 결정해주는 로드밸런싱 기능이 필요하다.
  • Service가 고정된 IP로 Pod에 접근할 수 있게 해준다. Kubernetes에서는 Service가 Pod의 한계를 보완해주는 역할을 한다. Service는 4개의 Type-ClusterIP, NodePort, LoadBalancer, ExternalName-으로 나누어져 있다. 각 타입별로 네트워크 구성이 달라진다. 상용 서비스에서 외부로 IP를 노출시킬 때는 LoadBalancer Type을 주로 사용한다. 더 상세한 설명은 여기를 참고
  • Service type 중 LoadBalancer type을 쓰려면 External IP를 만들어줘야한다. LoadBalancer type을 쓰려면 로드밸런서에 외부에서 접근가능한 External IP(외부 IP)를 만들어 줘야 한다. 기존에는 이 LoadBalancer Type의 Kubernetes Service 객체를 만들려면 클라우드 공급자(ex IBM 클라우드, AWS, GCP 등)가 제공해주는 IP를 사용해야 했다. 이 IP가 없으면 External IP에 영원히 pending이라고 찍혀있는 것을 봐야했다. 그럼 만약 클라우드 서비스를 못쓰는 환경이라면 어떻게 해야할까
  • **MetalLB를 활용하면 클라우드 공급자에서 실행되지 않는 클러스터에서도 External IP를 할당할 수 있고, 결과적으로 LoadBalancer Type의 Service 객체를 만들 수 있다.** 이름부터 BareMetal LoadBalancer.

kubespray 에는 group_vars 를 변경하는 것으로 아주 간단하게 metallb 를 설치할 수 있다.

아래를 참고하여 설치할 수 있다.

~/kubespray/inventory/mycluster/group_vars/k8s_cluster/addons.yml

...
138 # MetalLB deployment
139 metallb_enabled: true
140 metallb_speaker_enabled: true
141 metallb_ip_range:
142   - "192.168.100.240-192.168.100.249"
...
168 metallb_protocol: "layer2"
...

~/kubespray/inventory/mycluster/group_vars/k8s_cluster/k8s_cluster.yml

129 kube_proxy_strict_arp: true
cd ~/kubespray
ansible-playbook -i inventory/mycluster/inventory.ini cluster.yml

Metallb 는 Layer2 모드(소규모 10대 미만)와 BGP 모드(대규모 10대 이상) 두 가지 방식이 있다. 디폴트는 layer2 방식이다.

Metallb 는 Layer2 모드로 실행할 경우 각각의 노드에 speaker 파드가 띄워진다. 그리고 contoller 파드가 띄워지는데 이게 바로 LB 역할을 해준다.

정리하면 사용자가 speaker 파드의 IP 주소로 접속을 하면 speaker 파드는 controller 파드로 보내준다. 이 controller 가 LB 역할을 하고 뒷단에 연결된 NodePort 중 하나로 보내주고 NodePort 가 연결된 Service 로 보내주게 된다. 그 후에 Service 에 연결된 파드들로 분배된다.

즉, 정리하자면 speker → controller(LB) → NodePort → SVC → Pod 로 이동한다.

BGP 모드를 사용하기 위해서는 물리적인 L3 스위치가 있어야 한다.

L3 스위치가 로드 밸런서의 역할을 하고 서비스로 분배해준다. 이게 옳게 된 구성이다.

Service - ExternalName

클러스터 내부에서 클러스터 외부의 특정 서비스에 접속하기 위해 DNS CANME 을 설정

vagrant@node-1 svc/extname » cat weather-svc.yml
apiVersion: v1
kind: Service
metadata:
  name: weather-service
spec:
  type: ExternalName
  externalName: wttr.in

vagrant@node-1 svc/extname » kubectl get svc
NAME              TYPE           CLUSTER-IP     EXTERNAL-IP       PORT(S)        AGE
kubernetes        ClusterIP      10.233.0.1     <none>            443/TCP        4d
myweb-svc-np      LoadBalancer   10.233.22.31   192.168.100.240   80:31313/TCP   49m
weather-service   ExternalName   <none>         google.com        <none>         10m

외부 IP

하나 이상의 클러스터 노드로 라우팅되는 외부 IP가 있는 경우, 쿠버네티스 서비스는 이러한 externalIPs에 노출될 수 있다. 서비스 포트에서 외부 IP (목적지 IP)를 사용하여 클러스터로 들어오는 트래픽은 서비스 엔드포인트 중 하나로 라우팅된다. externalIPs는 쿠버네티스에 의해 관리되지 않으며 클러스터 관리자에게 책임이 있다.

서비스 명세에서, externalIPs는 모든 ServiceTypes와 함께 지정할 수 있다. 아래 예에서, 클라이언트는 "80.11.12.10:80"(외부 IP:포트)로 "my-service"에 접근할 수 있다.

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 9376
  externalIPs:
    - 80.11.12.10

Service - Ingress

공식 문서: https://kubernetes.io/ko/docs/concepts/services-networking/ingress-controllers/

인그레스 컨트롤러

인그레스 리소스가 작동하려면, 클러스터는 실행 중인 인그레스 컨트롤러가 반드시 필요하다.

kube-controller-manager 바이너리의 일부로 실행되는 컨트롤러의 다른 타입과 달리 인그레스 컨트롤러는 클러스터와 함께 자동으로 실행되지 않는다. 클러스터에 가장 적합한 인그레스 컨트롤러 구현을 선택하는데 이 페이지를 사용한다.

프로젝트로서 쿠버네티스는 AWS, GCE와 nginx 인그레스 컨트롤러를 지원하고 유지한다.

인그레스(Ingress)

클러스터 내의 서비스에 대한 외부 접근을 관리하는 API 오브젝트이며, 일반적으로 HTTP를 관리함.

인그레스는 부하 분산, SSL Termination, 명칭 기반의 가상 호스팅을 제공할 수 있다.

인그레스는 클러스터 외부에서 클러스터 내부 서비스로 HTTP와 HTTPS 경로를 노출한다. 트래픽 라우팅은 인그레스 리소스에 정의된 규칙에 의해 컨트롤된다.

다음은 인그레스가 모든 트래픽을 하나의 서비스로 보내는 간단한 예시이다.

서비스의 타입은 일반적으로 NodePort 타입이다.

인그레스는 외부에서 서비스로 접속이 가능한 URL, 로드 밸런스 트래픽, SSL / TLS 종료 그리고 이름-기반의 가상 호스팅을 제공하도록 구성할 수 있다. 인그레스 컨트롤러는 일반적으로 로드 밸런서를 사용해서 인그레스를 수행할 책임이 있으며, 트래픽을 처리하는데 도움이 되도록 에지 라우터 또는 추가 프런트 엔드를 구성할 수도 있다.

인그레스는 임의의 포트 또는 프로토콜을 노출시키지 않는다. HTTP와 HTTPS 이외의 서비스를 인터넷에 노출하려면 보통 Service.Type=NodePort 또는 Service.Type=LoadBalancer 유형의 서비스를 사용한다.

ingress-example.yml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: minimal-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx-example
  rules:
  - http:
      paths:
      - path: /testpath
        pathType: Prefix
        backend:
          service:
            name: test
            port:
              number: 80

여기서 rules 는 라우팅 규칙을 뜻한다

pathType

  • Exact: URL 경로의 대소문자를 엄격하게 일치시킨다.
  • Prefix: URL 경로의 접두사를 / 를 기준으로 분리한 값과 일치시킨다. 일치는 대소문자를 구분하고, 요소별로 경로 요소에 대해 수행한다. 모든 p 가 요청 경로의 요소별 접두사가 p 인 경우 요청은 p 경로에 일치한다.
  • implementationSpecific: 이 경로 유형의 일치 여부는 IngressClass에 따라 달라진다. 이를 구현할 때 별도 pathType 으로 처리하거나, Prefix 또는 Exact 경로 유형과 같이 동일하게 처리할 수 있다.

backend

  • resource: 다른 쿠버네티스의 리소스 그룹을 넣어줄 수 있다.
    • api Group
    • kind
    • name
  • service
    • name: 실제 서비스의 이름을 지정한다.
    • port:
      • name: 포트 이름
      • number: 포트 번호

전제 조건들

인그레스 컨트롤러가 있어야 인그레스를 충족할 수 있다. 인그레스 리소스만 생성한다면 효과가 없다.

ingress-nginx와 같은 인그레스 컨트롤러를 배포해야 할 수도 있다. 여러 인그레스 컨트롤러 중에서 선택할 수도 있다.

이상적으로, 모든 인그레스 컨트롤러는 참조 사양이 맞아야 한다. 실제로, 다양한 인그레스 컨트롤러는 조금 다르게 작동한다.

kubespray 에 ingress-nginx 설치

간단하게 애드온 설정 파일에서 ingress-nginx 를 true 로 설정하면 된다.

Niginx Ingress Controller

~/kubespray/inventory/mycluster/group_vars/k8s-cluster/addons.yml

 93 ingress_nginx_enabled: true

metrics-server

~/kubespray/inventory/mycluster/group_vars/k8s-cluster/addons.yml

 16 metrics_server_enabled: true

아래 명령어를 사용하여 애드온을 kubespray 에 적용한다.

cd ~/kubespray
ansible-playbook -i inventory/mycluster/inventory.ini cluster.yml -b

인그레스 리소스

다른 모든 쿠버네티스 리소스와 마찬가지로 인그레스에는 apiVersion, kind, 그리고 metadata 필드가 필요하다.

인그레스 오브젝트의 이름은 유효한 DNS 서브도메인 이름이어야 한다. 설정 파일의 작성에 대한 일반적인 내용은 애플리케이션 배포하기, 컨테이너 구성하기리소스 관리하기를 참조한다.

인그레스는 종종 어노테이션을 이용해서 인그레스 컨트롤러에 따라 몇 가지 옵션을 구성하는데, 그 예시는 재작성-타겟 어노테이션이다. 서로 다른 인그레스 컨트롤러는 서로 다른 어노테이션을 지원한다. 지원되는 어노테이션을 확인하려면 선택한 인그레스 컨트롤러의 설명서를 검토한다.

인그레스 사양 에는 로드 밸런서 또는 프록시 서버를 구성하는데 필요한 모든 정보가 있다. 가장 중요한 것은, 들어오는 요청과 일치하는 규칙 목록을 포함하는 것이다. 인그레스 리소스는 HTTP(S) 트래픽을 지시하는 규칙만 지원한다.

인그레스 규칙

각 HTTP 규칙에는 다음의 정보가 포함된다.

  • 선택적 호스트. 이 예시에서는, 호스트가 지정되지 않기에 지정된 IP 주소를 통해 모든 인바운드 HTTP 트래픽에 규칙이 적용 된다. 만약 호스트가 제공되면(예, foo.bar.com), 규칙이 해당 호스트에 적용된다.
  • 경로 목록 (예, /testpath)에는 각각 service.name 과 service.port.name 또는 service.port.number 가 정의되어 있는 관련 백엔드를 가지고 있다. 로드 밸런서가 트래픽을 참조된 서비스로 보내기 전에 호스트와 경로가 모두 수신 요청의 내용과 일치해야 한다.
  • 백엔드는 서비스 문서 또는 사용자 정의 리소스 백엔드에 설명된 바와 같이 서비스와 포트 이름의 조합이다. 호스트와 규칙 경로가 일치하는 인그레스에 대한 HTTP(와 HTTPS) 요청은 백엔드 목록으로 전송된다.

defaultBackend 는 종종 사양의 경로와 일치하지 않는 서비스에 대한 모든 요청을 처리하도록 인그레스 컨트롤러에 구성되는 경우가 많다.

DefaultBackend

규칙이 없는 인그레스는 모든 트래픽을 단일 기본 백엔드로 전송한다. defaultBackend 는 일반적으로 인그레스 컨트롤러의 구성 옵션이며, 인그레스 리소스에 지정되어 있지 않다.

만약 인그레스 오브젝트의 HTTP 요청과 일치하는 호스트 또는 경로가 없으면, 트래픽은 기본 백엔드로 라우팅 된다.

리소스 백엔드

Resource 백엔드는 인그레스 오브젝트와 동일한 네임스페이스 내에 있는 다른 쿠버네티스 리소스에 대한 ObjectRef이다. Resource 는 서비스와 상호 배타적인 설정이며, 둘 다 지정하면 유효성 검사에 실패한다. Resource 백엔드의 일반적인 용도는 정적 자산이 있는 오브젝트 스토리지 백엔드로 데이터를 수신하는 것이다.

 

아래와 같은 방식으로 구성을 할 수 있다.

myweb-rs-np.yml

vagrant@node-1 cont/ing » cat myweb-rs-np.yml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myweb-rs-set
spec:
  replicas: 3
  selector:
    matchExpressions:
      - key: app
        operator: In
        values:
          - web
      - key: env
        operator: Exists
  template:
    metadata:
      labels:
        app: web
        env: dev
    spec:
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb
          ports:
            - containerPort: 8080
              protocol: TCP

myweb-svc-np.yml

vagrant@node-1 cont/ing » cat myweb-svc-np.yml
apiVersion: v1
kind: Service
metadata:
  name: myweb-svc-np
spec:
  type: NodePort
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080

myweb-ing.yml

vagrant@node-1 cont/ing » cat myweb-ing.yml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress
spec:
  rules:
  - http:
      paths:
      - pathType: Prefix
        path: /
        backend:
          service:
            name: myweb-svc-np
            port:
              number: 80

도메인을 구매하지 않고 호스트 필드 사용하기

vagrant@node-1 cont/ing » cat myweb-ing.yml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress
spec:
  rules:
	- host: '*.test.xyz'
    http:
      paths:
      - pathType: Prefix
        path: /
        backend:
          service:
            name: myweb-svc-np
            port:
              number: 80

방법 1

curl --resolve 옵션을 사용

curl --resolve <host>:<port>:<ingress_ip> <host>

curl --resolve www.test.xyz:80:192.168.100.103 www.test.xyz

방법 2

/etc/nsswitch.conf 에서 hosts 가 files dns 로 되어있는지 확인

/etc/hosts 파일 수정

...
192.168.100.103 www.test.xyz
curl <http://www.test.xyz>

방법 3

https://nip.io/

와일드 카드 DNS 서비스를 사용한다.

와일드 카드 DNS: 어떤 URL 을 요청해도 response 를 돌려준다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress
spec:
  rules:
    - host: '*.nip.io'
      http:
        paths:
        - pathType: Prefix
          path: /
          backend:
            service:
              name: myweb-svc-np
              port:
                number: 80
vagrant@node-1 cont/ing » curl <http://app-192-168-100-103.nip.io>
Hello World!
myweb-rs-set-l6z59

Ingress 예제

  • 이미지 생성 및 레지스트리에 푸시
  • 레플리카셋 구성
  • 서비스 구성(NodePort 타입)
  • 인그레스 구성

hello:one 이미지 Dockerfile

FROM httpd
COPY index.html /usr/local/apache2/htdocs/index.html

index.html

<h1> Hello One </h1>

hello:two 이미지 Dockerfile

FROM httpd
COPY index.html /usr/local/apache2/htdocs/index.html

index.html

<h1> Hello Two </h1>
docker image build X/hello:one
docker image build X/hello:two
docker login
docker push X/hello:one
docker push X/hello:two

one-rs.yaml

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: one-rs
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-one
  template:
    metadata:
      labels:
        app: hello-one
    spec:
      containers:
        - name: hello-one
          image: c1t1d0s7/hello:one
          ports:
            - containerPort: 80
              protocol: TCP

two-rs.yaml

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: two-rs
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-two
  template:
    metadata:
      labels:
        app: hello-two
    spec:
      containers:
        - name: hello-two
          image: c1t1d0s7/hello:two
          ports:
            - containerPort: 80
              protocol: TCP

one-svc-np.yaml

apiVersion: v1
kind: Service
metadata:
  name: one-svc-np
spec:
  type: NodePort
  selector:
    app: hello-one
  ports:
    - port: 80
      targetPort: 80

two-svc-np.yaml

apiVersion: v1
kind: Service
metadata:
  name: two-svc-np
spec:
  type: NodePort
  selector:
    app: hello-two
  ports:
    - port: 80
      targetPort: 80

hello-ing.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: hello-ing
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: '*.nip.io'
      http:
        paths:
          - path: /one
            pathType: Prefix
            backend:
              service:
                name: one-svc-np
                port:
                  number: 80
          - path: /two
            pathType: Prefix
            backend:
              service:
                name: two-svc-np
                port:
                  number: 80

x.nip.io/one 으로 접속을 하게 되면 pod_ip/one 주소로 접속하게 된다. 현재 /one 경로에는 아무런 값이 없기 때문에 x.nip.io/one 으로 접속해도 / 경로로 접속이 되어야 한다.

그래서 어노테이션에서 nginx.ingress.kubernetes.io/rewrite-target: / 을 사용했다. rewrite-target: / 은 어떤 경로로 들어오든 간에 타겟 경로를 / 경로로 들어오도록 변경하는 것이다.

kubectl create -f .

curl http://192-168-100-103.nip.io/one
<h1> Hello One </h1>

curl http://192-168-100-103.nip.io/two
<h1> Hello Two </h1>

Readiness Probe

서비스에 파드를 연결시키기 전에 파드의 상태가 정상적인지 확인(헬스 체크)을 하고 엔드포인트에 등록을 시켜준다.

myweb-rs-set.yml

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myweb-rs-set
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
      env: dev
  template:
    metadata:
      labels:
        app: web
        env: dev
    spec:
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb:alpine
          ports:
            - containerPort: 8080
              protocol: TCP
          readinessProbe:
            exec:
              command:
                - ls
                - /tmp/ready

myweb-svc-np.yml

apiVersion: v1
kind: Service
metadata:
  name: myweb-svc-np
spec:
  type: LoadBalancer
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 31313

readiness probe 가 ls /tmp/ready 명령어를 실행하여 제대로 작동을 하는지 헬스 체크를 한다. 하지만 현재 pod 에는 해당 파일이 존재하지 않기 때문에 비정상적인 파드라고 판단을 하여 파드가 준비 상태로 변하지 않는다. 따라서 endpoint 에 파드의 IP 주소가 등록되지 않는다.

vagrant@node-1 cont/readiness » kubectl get pod,svc,rs,ep
NAME                     READY   STATUS    RESTARTS   AGE
pod/myweb-rs-set-b7mxc   0/1     Running   0          7m48s
pod/myweb-rs-set-wknpx   0/1     Running   0          7m48s
pod/myweb-rs-set-x74kb   0/1     Running   0          7m48s

NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP       PORT(S)        AGE
service/kubernetes     ClusterIP      10.233.0.1     <none>            443/TCP        6d21h
service/myweb-svc-np   LoadBalancer   10.233.53.30   192.168.100.240   80:31313/TCP   7m48s

NAME                           DESIRED   CURRENT   READY   AGE
replicaset.apps/myweb-rs-set   3         3         0       36s

NAME                     ENDPOINTS              AGE
endpoints/kubernetes     192.168.100.103:6443   6d21h
endpoints/myweb-svc-np                          36s

아래 명령어를 사용하여 파드에 /tmp/ready 파일을 생성하면 pod 가 준비 상태가 되고 서비스에 연결된다.

kubectl exec <POD> -- touch /tmp/ready
vagrant@node-1 cont/readiness » kubectl exec myweb-rs-set-b7mxc -- touch /tmp/ready
vagrant@node-1 cont/readiness » kubectl exec myweb-rs-set-wknpx -- touch /tmp/ready
vagrant@node-1 cont/readiness » kubectl exec myweb-rs-set-x74kb -- touch /tmp/ready

vagrant@node-1 cont/readiness » kubectl get pod,svc,rs,ep
NAME                     READY   STATUS    RESTARTS   AGE
pod/myweb-rs-set-b7mxc   1/1     Running   0          7m48s
pod/myweb-rs-set-wknpx   1/1     Running   0          7m48s
pod/myweb-rs-set-x74kb   1/1     Running   0          7m48s

NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP       PORT(S)        AGE
service/kubernetes     ClusterIP      10.233.0.1     <none>            443/TCP        6d21h
service/myweb-svc-np   LoadBalancer   10.233.53.30   192.168.100.240   80:31313/TCP   7m48s

NAME                           DESIRED   CURRENT   READY   AGE
replicaset.apps/myweb-rs-set   3         3         3       7m48s

NAME                     ENDPOINTS                                                 AGE
endpoints/kubernetes     192.168.100.103:6443                                      6d21h
endpoints/myweb-svc-np   10.233.109.23:8080,10.233.112.42:8080,10.233.69.54:8080   7m48s
728x90