KANS3 - Kubernetes Ingress & Gateway API - 01/02

Ingress

인그레스는 쿠버네티스 내부의 서비스를 외부로 노출하되 L7으로 노출하는 역할로 프록시와 비슷한 역할을 수행합니다. K8s에서는 Ingress는 API로만 구현이 되어있고, 별도의 구현체(Nginx, Kong 등)으로 사용하게됩니다. 인그레스는 앞 문장에 서술했듯이 Service는 L4 수준의 통신이라면, 인그레스는 L7 수준의 통신으로 SSL 관련 처리까지 가능해서, 앞에서는 SSL 처리를 완료하고 뒤에서는 http로 통신을 할수 있기도 합니다. 하지만, 이제는 Gateway API가 새로이 나오면서 Ingress API는 더이상 추가 개발을 하지 않는 상태가 되었습니다.

통신흐름

# 인스턴스 모드, Ingress -> Service -> Application
Client => LB[Service - Ingress Controller Pod] => Service => POD

# IP모드, Ingress -> Application
Client => LB[Service - Ingress Controller Pod] => POD

지원 기능

HTTP/HTTPS 트래픽을 호스트(Doamin) 기반 라우팅으로 부하분산, 카나리 지원으로 유연한 어플리케이션 업데이트, SSL/TLS 종료 지원

설정 동기화

Nginx Ingress Controller의 목표중 하나는 설정파일을 자동으로 적용하는 것으로 ConfigMap에 nginx.conf 파일설정 변경시 자동으로 reload 되도록 Lua로 작성된 모듈 사용

실습

Nginx Ingress Controller 배포

# manifest 파일 생성
cat << EOT > ingress-nginx-values.yaml
controller:
  service:
    type: NodePort
    nodePorts:
      http: 30080
      https: 30443
  nodeSelector:
    kubernetes.io/hostname: "k3s-s"
  metrics:
    enabled: true
  serviceMonitor:
      enabled: true
EOT
# 네임스페이스 생성
k create ns ingress

# ingress-nginx helm chart 배포
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

helm install ingress-nginx ingress-nginx/ingress-nginx -f ingress-nginx-values.yaml --namespace ingress --version 4.11.2

# 확인
k get all -n ingress
---
NAME                                           READY   STATUS              RESTARTS   AGE
pod/ingress-nginx-controller-979fc89cf-rk86h   0/1     ContainerCreating   0          16s

NAME                                         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGE
service/ingress-nginx-controller             NodePort    10.10.200.90   <none>        80:30080/TCP,443:30443/TCP   16s
service/ingress-nginx-controller-admission   ClusterIP   10.10.200.52   <none>        443/TCP                      16s
service/ingress-nginx-controller-metrics     ClusterIP   10.10.200.30   <none>        10254/TCP                    16s

NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/ingress-nginx-controller   0/1     1            0           16s

NAME                                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/ingress-nginx-controller-979fc89cf   1         1         0       16s
---
k describe svc -n ingress ingress-nginx-controller

# externalTrafficPolicy 를 local 로 설정
k patch svc -n ingress ingress-nginx-controller -p '{"spec":{"externalTrafficPolicy": "Local"}}'
# 기본 nginx conf 파일 확인
k describe cm -n ingress ingress-nginx-controller
---
Name:         ingress-nginx-controller
Namespace:    ingress
Labels:       app.kubernetes.io/component=controller
              app.kubernetes.io/instance=ingress-nginx
              app.kubernetes.io/managed-by=Helm
              app.kubernetes.io/name=ingress-nginx
              app.kubernetes.io/part-of=ingress-nginx
              app.kubernetes.io/version=1.11.2
              helm.sh/chart=ingress-nginx-4.11.2
Annotations:  meta.helm.sh/release-name: ingress-nginx
              meta.helm.sh/release-namespace: ingress

Data
====
allow-snippet-annotations:
----
false

BinaryData
====

Events:
  Type    Reason  Age   From                      Message
  ----    ------  ----  ----                      -------
  Normal  CREATE  60s   nginx-ingress-controller  ConfigMap ingress/ingress-nginx-controller
---

k exec deploy/ingress-nginx-controller -n ingress -it -- cat /etc/nginx/nginx.conf
---
# Configuration checksum: 13949063550686361986

# setup custom paths that do not require root access
pid /tmp/nginx/nginx.pid;

daemon off;

worker_processes 2;

worker_rlimit_nofile 1047552;

worker_shutdown_timeout 240s ;

events {
    multi_accept        on;
    worker_connections  16384;
    use                 epoll;

}

http {

    lua_package_path "/etc/nginx/lua/?.lua;;";

    lua_shared_dict balancer_ewma 10M;
    lua_shared_dict balancer_ewma_last_touched_at 10M;
    lua_shared_dict balancer_ewma_locks 1M;
    lua_shared_dict certificate_data 20M;
    lua_shared_dict certificate_servers 5M;
    lua_shared_dict configuration_data 20M;
    lua_shared_dict global_throttle_cache 10M;
    lua_shared_dict ocsp_response_cache 5M;
---

# Nginx Ingress Controller의 버전 확인
POD_NAMESPACE=ingress
POD_NAME=$(kubectl get pods -n $POD_NAMESPACE -l app.kubernetes.io/name=ingress-nginx --field-selector=status.phase=Running -o name)
kubectl exec $POD_NAME -n $POD_NAMESPACE -- /nginx-ingress-controller --version
---
NGINX Ingress controller
  Release:       v1.11.2
  Build:         46e76e5916813cfca2a9b0bfdc34b69a0000f6b9
  Repository:    https://github.com/kubernetes/ingress-nginx
  nginx version: nginx/1.25.5
---

실습용 오브젝트 배포

cat <<EOT > svc1-pod.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy1-websrv
spec:
  replicas: 1
  selector:
    matchLabels:
      app: websrv
  template:
    metadata:
      labels:
        app: websrv
    spec:
      containers:
      - name: pod-web
        image: nginx
---
apiVersion: v1
kind: Service
metadata:
  name: svc1-web
spec:
  ports:
    - name: web-port
      port: 9001
      targetPort: 80
  selector:
    app: websrv
  type: ClusterIP
EOT

cat <<EOT > svc2-pod.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy2-guestsrv
spec:
  replicas: 2
  selector:
    matchLabels:
      app: guestsrv
  template:
    metadata:
      labels:
        app: guestsrv
    spec:
      containers:
      - name: pod-guest
        image: gcr.io/google-samples/kubernetes-bootcamp:v1
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: svc2-guest
spec:
  ports:
    - name: guest-port
      port: 9002
      targetPort: 8080
  selector:
    app: guestsrv
  type: NodePort
EOT

cat <<EOT > svc3-pod.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy3-adminsrv
spec:
  replicas: 3
  selector:
    matchLabels:
      app: adminsrv
  template:
    metadata:
      labels:
        app: adminsrv
    spec:
      containers:
      - name: pod-admin
        image: k8s.gcr.io/echoserver:1.5
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: svc3-admin
spec:
  ports:
    - name: admin-port
      port: 9003
      targetPort: 8080
  selector:
    app: adminsrv
EOT

# 배포
k apply -f svc1-pod.yml
k apply -f svc2-pod.yml
k apply -f svc3-pod.yml

정책 배포

# Ingress 정책 생성 및 배포
cat <<EOT> ingress1.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-1
  annotations:
    #nginx.ingress.kubernetes.io/upstream-hash-by: "true"
spec:
  ingressClassName: nginx
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: svc1-web
            port:
              number: 80
      - path: /guest
        pathType: Prefix
        backend:
          service:
            name: svc2-guest
            port:
              number: 8080
      - path: /admin
        pathType: Prefix
        backend:
          service:
            name: svc3-admin
            port:
              number: 8080
EOT

k apply -f ingress1.yaml
# 생성확인
kubectl get ingress
---
NAME        CLASS   HOSTS   ADDRESS   PORTS   AGE
ingress-1   nginx   *                 80      26s
---

kc describe ingress ingress-1
---
Name:             ingress-1
Labels:           <none>
Namespace:        default
Address:
Ingress Class:    nginx
Default backend:  <default>
Rules:
  Host        Path  Backends
  ----        ----  --------
  *
              /        svc1-web:80 ()
              /guest   svc2-guest:8080 ()
              /admin   svc3-admin:8080 ()
Annotations:  <none>
Events:
  Type    Reason  Age   From                      Message
  ----    ------  ----  ----                      -------
  Normal  Sync    40s   nginx-ingress-controller  Scheduled for sync
---

# 설정 반영 확인
kubectl exec deploy/ingress-nginx-controller -n ingress -it -- cat /etc/nginx/nginx.conf | grep 'location /' -A5
---
        location /guest/ {

            set $namespace      "default";
            set $ingress_name   "ingress-1";
            set $service_name   "svc2-guest";
            set $service_port   "8080";
--
  		location /admin/ {

            set $namespace      "default";
            set $ingress_name   "ingress-1";
            set $service_name   "svc3-admin";
            set $service_port   "8080";
--
  		location / {

            set $namespace      "default";
            set $ingress_name   "ingress-1";
            set $service_name   "svc1-web";
            set $service_port   "80";
--
  		location /healthz {

            access_log off;
            return 200;
        }

--
  		location /nginx_status {

            allow 127.0.0.1;

            allow ::1;
---

접속테스트

echo -e "Ingress1 sv1-web URL = http://$(curl -s ipinfo.io/ip):30080"
echo -e "Ingress1 sv2-guest URL = http://$(curl -s ipinfo.io/ip):30080/guest"
echo -e "Ingress1 sv3-admin URL = http://$(curl -s ipinfo.io/ip):30080/admin"

X-Forwarded-For / X-Forwarded-Proto 헤더?

X-Forwarded-For 는 클라이언트 IP를 포함하여 거쳐가는 환경에 대한 IP를 저장합니다.

X-Forwarded-Proto 는 프로토콜이 변하기 전 정보를 저장합니다.

X-Forwarded-For의 헤더는 Client Ip, Proxy(LB), Proxy(LB)… 등등 ,(콤마)로 구분되어 순차 기록되는데요.

어플리케이션에서 실제 클라이언트의 IP를 알고자 한다면, Client IP가 가장 먼저 오기 때문에 첫번째로 오는 IP로 구분을 하도록 작성합니다.

이 부분은 AccessLog 설정에도 사용한다면 정확한 클라이언트의 Ip 확인이 가능합니다.