[Kubernetes ingress part-1] Nginx ingress 구현과 사용 방법
K8s(쿠버네티스) 클러스터 내에 구현된 Application을 외부로 노출하여 서비스할 경우에 주로 Nginx의 LB 기능을 이용한 Ingress Load Balancer를 사용하게 된다. 기본적으로 제공되는 이러한 Nginx 컨테이너 방식 외에도 Haproxy, Traefik Ingress등의 3rd party 솔루션들이 존재하는데, 다음 part-2 에서는 최근 hot하게 뜨고 있는 Traefik Ingress를 사용한 구현을 다룰 예정이다.
필요 요소와 서비스 구성
- Kubernetes Cluster(Local cluster, minikube or dind-cluster not in the cloud like AWS or GCP)
- Backend Service(like web or specific app server)
- 테스트 수행용 컨테이너(curl로 클러스터 내부 접속 테스트)
- Local DNS(Cluster 외부, named 서버)
예제로 사용하는 백엔드 서비스는 단순한 web server로, 앞선 포스팅(http://bryan.wiki/287) 에서 만들어 본 웹서버 컨테이너 이미지(drlee001/kubeweb)을 사용한다.
위 그림은 본 구현에서 적용된 전체 요소들의 논리적인 구성과 관계를 나타낸 것이다. 텍스트가 작아서 잘 보이지 않을 경우, 이미지를 클릭하여 원본 크기로 자세히 들여다 보도록 하자.
테스트 수행용 컨테이너 기동
클러스터 내부의 각 서비스에 대한 접속 테스트를 curl 로 수행할 때 사용할 Pod로, 네트워크를 테스트하기 위해 많이 사용하는 drlee001/net-tester 컨테이너를 기동해 둔다.
<00-test-pod.yaml>
apiVersion: v1
kind: Pod
metadata:
name: net-tester-for-ingress
spec:
containers:
- image: drlee001/net-tester
imagePullPolicy: IfNotPresent
name: net-tester-container
restartPolicy: Never
[root@kubemaster nginx-ingress-sample]# kubectl create -f 00-test-pod.yaml
[root@kubemaster nginx-ingress-sample]# kubectl get pods | grep net-tester
net-tester-for-ingress 1/1 Running 0 13h
Backend Service 기동
Load Balancing 의 대상이 되는 예제 서비스로, 이미 만들어진 drlee001/kubeweb 컨테이너를 deploy 하고 ClusterIP 로 service를 기동해 둔다. 여기서는 2개의 서로 다른 서비스에 대해 하나의 Ingress를 통해 Load balancing이 가능하도록 deploy-test-1(port 7777로 접속) 과 deploy-test-2(port 8888로 접속)으로 구분하여 구축해 두도록 한다.
<01-deploy-2backends.yaml>
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: deploy-test-1
spec:
replicas: 2
template:
metadata:
labels:
app: web-front-end
department: group1
spec:
containers:
- name: tiny-webserver-1
image: drlee001/kubeweb
env:
- name: PORT_ARGS
value: "--port=7777"
ports:
- containerPort: 7777
name: web-port
protocol: TCP
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: deploy-test-2
spec:
replicas: 2
template:
metadata:
labels:
app: web-front-end
department: group2
spec:
containers:
- name: tiny-webserver-2
image: drlee001/kubeweb
env:
- name: PORT_ARGS
value: "--port=8888"
ports:
- containerPort: 8888
name: web-port
protocol: TCP
[root@kubemaster nginx-ingress-sample]# kubectl create -f 01-deploy-2backends.yaml
deployment "deploy-test-1" created
deployment "deploy-test-2" created
[root@kubemaster nginx-ingress-sample]# kubectl get deployment | grep deploy-test
deploy-test-1 2 2 2 2 13h
deploy-test-2 2 2 2 2 13h
다음으로, 2개의 서로 다른 deployment 에 대응하는 Cluster 내부 접속 endpoint 를 제공할 서비스를 구축한다.
<02-svc-2backends.yaml>
apiVersion: v1
kind: Service
metadata:
name: backend-svc-1
spec:
ports:
- port: 80
protocol: TCP
targetPort: web-port
selector:
app: web-front-end
department: group1
---
apiVersion: v1
kind: Service
metadata:
name: backend-svc-2
spec:
ports:
- port: 80
protocol: TCP
targetPort: web-port
selector:
app: web-front-end
department: group2
[root@kubemaster nginx-ingress-sample]# kubectl create -f 02-svc-2backends.yaml
service "backend-svc-1" created
service "backend-svc-2" created
[root@kubemaster nginx-ingress-sample]# kubectl get svc | grep backend-svc
backend-svc-1 ClusterIP 10.133.108.20 <none> 80/TCP 13h
backend-svc-2 ClusterIP 10.131.128.20 <none> 80/TCP 13h
Default backend 서비스 기동
Ingress LB를 통해서 들어온 request가, 등록된 Ingress rule(나중에 정의해서 사용할)에 매칭되지 않을 경우의 적당한 response 를 위해서, 이러한 request 를 받아줄 별도의 내부 서비스가 필요하다. 이를 default backend(일종의 랜딩페이지) 라고 하는데, port 8080으로 들어온 http 접속에 대해 하위 path가 '/' 인 경우 404(not found)를, '/healthz' 인 경우 200(ok) 를 응답하는 간단한 web server 이다. 여기서는 구글이 제공하는 defaultbackend 컨테이너를 사용한다.
<03-default-backend-for-ingress.yaml>
apiVersion: v1
kind: Service
metadata:
name: default-http-backend
spec:
type: ClusterIP
ports:
- port: 80
protocol: TCP
targetPort: 8080
# Matches Deployment
selector:
app: default-http-backend
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: default-http-backend
spec:
replicas: 2
template:
metadata:
# Matches Deployment
labels:
app: default-http-backend
spec:
terminationGracePeriodSeconds: 60
containers:
- name: default-http-backend
# Any image is permissable as long as:
# 1. It serves a 404 page at /
# 2. It serves 200 on a /healthz endpoint
image: gcr.io/google_containers/defaultbackend:1.0
livenessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: 30
timeoutSeconds: 5
ports:
- containerPort: 8080
resources:
limits:
cpu: 10m
memory: 20Mi
requests:
cpu: 10m
memory: 20Mi
[root@kubemaster nginx-ingress-sample]# kubectl create -f 03-default-backend-for-ingress.yaml
deployment "default-http-backend" created
service "default-http-backend" created
[root@kubemaster nginx-ingress-sample]# kubectl get svc | grep default-http
default-http-backend ClusterIP 10.142.34.97 <none> 80/TCP 20h
테스트용 컨테이너로 서비스 정상 동작 확인
[root@kubemaster nginx-ingress-sample]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
backend-svc-1 ClusterIP 10.133.108.20 <none> 80/TCP 20h
backend-svc-2 ClusterIP 10.131.128.20 <none> 80/TCP 20h
default-http-backend ClusterIP 10.142.34.97 <none> 80/TCP 20h
[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl http://backend-svc-1
<b> Pod/Hostname: deploy-test-1-76754b9b75-9p2lx Port: 7777</b><br/><br/>
[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl http://backend-svc-2
<b> Pod/Hostname: deploy-test-2-6f57b84975-9jnf5 Port: 8888</b><br/><br/>
[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl http://default-http-backend
default backend - 404
Nginx Ingress Controller 설정값 저장을 위한 Configmap 등록
Nginx는 웹서버 기능과 함께 로드밸런서 역할도 수행할 수 있게 만들어져 있다. 편리하게도 kubernetes 클러스터 내에 이 설정 값을 configmap 형태로 등록해 두고, 필요에 따라 설정값을 바꿔가면서 운영할 수 있는데, 진행 과정 상 Nginx Ingress Controller를 띄우기 전에 미리 등록해 두어야 한다.
<04-configmap-nginx-ingress-controller.yaml>
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-ingress-controller-conf
labels:
app: nginx-ingress-lb
group: lb
data:
# for VTS page of the Nginx load balancer
enable-vts-status: 'true'
enable-sticky-sessions: 'true'
[root@kubemaster nginx-ingress-sample]# kubectl create -f 04-configmap-nginx-ingress-controller.yaml
configmap "nginx-ingress-controller-conf" created
Nginx Ingress Controller 실행
<05-deploy-nginx-ingress-controller.yaml>
# Add: SA, ClusterRole, ClusterRoleBinding
# Name Space: default
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: ingress
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: system:ingress
rules:
- apiGroups:
- ""
resources: ["configmaps","secrets","endpoints","events","services"]
verbs: ["list","watch","create","update","delete","get"]
- apiGroups:
- ""
- "extensions"
resources: ["services","nodes","ingresses","pods","ingresses/status"]
verbs: ["list","watch","create","update","delete","get"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: ingress
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:ingress
subjects:
- kind: ServiceAccount
name: ingress
# Could be like 'kube-system' ...
namespace: default
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-ingress-controller
# Could be like 'kube-system' ...
namespace: default
spec:
replicas: 2
revisionHistoryLimit: 3
template:
metadata:
labels:
app: nginx-ingress-lb
spec:
serviceAccountName: ingress
terminationGracePeriodSeconds: 60
containers:
- name: nginx-ingress-controller
image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.2
imagePullPolicy: IfNotPresent
readinessProbe:
httpGet:
path: /healthz
port: 18080
scheme: HTTP
livenessProbe:
httpGet:
path: /healthz
port: 18080
scheme: HTTP
initialDelaySeconds: 10
timeoutSeconds: 5
args:
- /nginx-ingress-controller
- --default-backend-service=$(POD_NAMESPACE)/default-http-backend
- --configmap=$(POD_NAMESPACE)/nginx-ingress-controller-conf
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- containerPort: 80
- containerPort: 18080
[root@kubemaster nginx-ingress-sample]# kubectl create -f 05-deploy-nginx-ingress-controller.yaml
deployment "nginx-ingress-controller" created
[root@kubemaster nginx-ingress-sample]# kubectl get deployment nginx-ingress-controller
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
nginx-ingress-controller 2 2 2 2 20h
여기까지 진행하였다면 다음과 같은 여러 개의 pod 가 실행되고 있음을 확인할 수 있다.
[root@kubemaster nginx-ingress-sample]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
default-http-backend-7c7764588c-8w4qg 1/1 Running 0 20h 10.38.0.6 kubenode3
default-http-backend-7c7764588c-qtbfk 1/1 Running 0 20h 10.32.0.5 kubenode1
deploy-test-1-76754b9b75-9p2lx 1/1 Running 0 21h 10.40.0.5 kubenode2
deploy-test-1-76754b9b75-hwnwc 1/1 Running 0 21h 10.38.0.5 kubenode3
deploy-test-2-6f57b84975-9jnf5 1/1 Running 0 21h 10.40.0.6 kubenode2
deploy-test-2-6f57b84975-zhrs8 1/1 Running 0 21h 10.32.0.3 kubenode1
net-tester-for-ingress 1/1 Running 0 21h 10.32.0.2 kubenode1
nginx-ingress-controller-6679d99f68-gzkhb 1/1 Running 0 20h 10.40.0.7 kubenode2
nginx-ingress-controller-6679d99f68-wnshp 1/1 Running 0 20h 10.32.0.6 kubenode1
이 중에서 실제로 로드밸런서 역할을 담당할 pod가 nginx-ingress-controller-*-* 같이 생긴 pod들이다(사실 하나만 띄워도 되지만, 고가용성-HA-를 위해서는 2개 이상을 사용). net-tester를 통해서 각각의 nginx-ingress-controller 에 직접 접속이 되는지 확인해 보자. 아직은 따로 Ingress Rule을 적용하지 않았기 때문에 '404' 응답이 나오면 정상적인 상태이다(실은, 이 '404' 응답은 그림에서 보이듯이 default-http-backend 에서 보내주는 것이다).
[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl http://10.40.0.7
default backend - 404
[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl http://10.32.0.6
default backend - 404
Ingress Rule 디플로이
이제 모든 준비는 끝났고, Nginx Ingress Controller 가 로드밸런서 역할을 수행할 수 있도록 Ingress Rule(Policy, 또는 Ingress Object) 를 디플로이하고 서비스로 노출(Expose)시키는 단계와, curl을 통한 접속 테스트, DNS 등록을 통한 실제 접속을 해 보는 일만 남았다. 아래 yaml을 보면 총 3개의 도메인에 대한 rule이 존재하는데, Nginx 또는 Apache httpd 등으로 웹서버를 운영해 보았다면, 하나의 웹서버에서 서로 다른 도메인에 대해 각각 특정한 웹페이지를 연결해 주는 'VirtualHost' 의 동작 방식과 유사함을 보게 될 것이다.
<06-ingress-rule.yaml>
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx-ingress
namespace: default
spec:
rules:
- host: kubeweb-7777.bryan.local
http:
paths:
- backend:
serviceName: backend-svc-1
servicePort: 80
- host: kubeweb-8888.bryan.local
http:
paths:
- backend:
serviceName: backend-svc-2
servicePort: 80
- host: kubeweb.bryan.local
http:
paths:
- path: /svc1
backend:
serviceName: backend-svc-1
servicePort: 80
- path: /svc2
backend:
serviceName: backend-svc-2
servicePort: 80
- path: /nginx_status
backend:
serviceName: nginx-ingress-nodeport
servicePort: 18080
[root@kubemaster nginx-ingress-sample]# kubectl create -f 06-ingress-rule.yaml
ingress "nginx-ingress" created
[root@kubemaster nginx-ingress-sample]# kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
nginx-ingress kubeweb-7777.bryan.local,kubeweb-8888.bryan.local,kubeweb.bryan.local 80 20h
여기까지는 Kubernetes 클러스터 내부에서의 연결 통로만 설정이 완료된 상태다.
[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl -H Host:kubeweb-7777.bryan.local http://10.40.0.7
<b> Pod/Hostname: deploy-test-1-76754b9b75-hwnwc Port: 7777</b><br/><br/>
[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl -H Host:kubeweb-8888.bryan.local http://10.40.0.7
<b> Pod/Hostname: deploy-test-2-6f57b84975-zhrs8 Port: 8888</b><br/><br/>
* Host Header 를 조작하여 접속을 시도하는 세션에 도메인명을 담아서 접속하면 Ingress Rule 에 따라 2개의 서비스 각각에 분기 접속되는 것을 확인할 수 있다(위의 06-ingress-rule.yaml에서 정의된 첫 번 째, 두 번 째 rule에 매칭된다)
[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl -H Host:kubeweb.bryan.local http://10.40.0.7
default backend - 404
[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl -H Host:kubeweb.bryan.local http://10.40.0.7/svc1
<b> Pod/Hostname: deploy-test-1-76754b9b75-9p2lx Port: 7777</b><br/><br/>
[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl -H Host:kubeweb.bryan.local http://10.40.0.7/svc2
<b> Pod/Hostname: deploy-test-2-6f57b84975-9jnf5 Port: 8888</b><br/><br/>
* 위와 비슷하지만 이번에는 kubeweb,bryan.local 도메인에 대한 URL path 별로 접속이 분기됨을 확인할 수 있다(06-ingress-rule.yaml에서 정의된 세 번 째 rule에 매칭된다)
Ingress 서비스 노출(Expose)
Kubernetes Cluster 의 외부에서 웹브라우저 등을 통해서 내부에 존재하는 backend-svc-1, backend-svc-2 에 접속하기 위해서는 NodePort 방식으로 접속하면 된다(AWS나 Google Cloud Engine과 같은 퍼블릭클라우드에 Kubernetes 가 설치된 경우에는 Loadbalancer 방식으로 접속하면 된다).
<07-svc-expose-by-nodeport.yaml>
apiVersion: v1
kind: Service
metadata:
name: nginx-ingress-nodeport
spec:
type: NodePort
ports:
- port: 80
nodePort: 30100
name: http
- port: 18080
nodePort: 30101
name: http-mgmt
selector:
app: nginx-ingress-lb
[root@kubemaster nginx-ingress-sample]# kubectl create -f 07-svc-expose-by-nodeport.yaml
service "nginx-ingress-nodeport" created
[root@kubemaster nginx-ingress-sample]# kubectl get svc nginx-ingress-nodeport -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
nginx-ingress-nodeport NodePort 10.138.77.194 <none> 80:30100/TCP,18080:30101/TCP 21h app=nginx-ingress-lb
이제 클러스터 외부에서 웹브라우저를 통해 NodePort 로 접속을 시도해 보아야 할텐데, 그 전에 한 가지 더 할 일이 있다. 클러스터 외부의 DNS의 존파일에 여기서 사용할 도메인의 A 레코드들을 등록해 주어야 한다. 다음과 같이 DNS 서버에 추가로 작업해 주고 'systemctl restart named' 로 named 서비스를 재시작한다.
[root@bryan-dns ~]# vi /var/named/bryan.local.zone
...
; For testing nginx-ingress
kubeweb-7777 IN A 10.255.10.171
IN A 10.255.10.172
IN A 10.255.10.173
kubeweb-8888 IN A 10.255.10.171
IN A 10.255.10.172
IN A 10.255.10.173
kubeweb IN A 10.255.10.171
IN A 10.255.10.172
IN A 10.255.10.173
웹브라우저로 Ingress 접속 확인
다음 캡처 이미지는 ConfigMap 에서 enable-vts-status 로 사용하도록 하고 ingress-rule.yaml 에서 연결되도록 설정하고 NodePort로 expose시킨 Ingress Status 모니터링 화면이다.
* Reference: dasblinkenlichten.com 포스팅 내용과 아이디어를 다수 참조 하였음
* 다음 포스팅인 Part-2 에서는 Traefik Ingress를 활용한 Ingress 로드밸런서를 구현해 보고, Nginx 방식에 비해 어떤 점이 다르고 또 장점은 무엇인지 생각하는 시간을 가져보기로 한다. 구현과 테스트 방식은 이번 Part-1 과 유사하며, 양 쪽에 놓고 비교해 보는 자료로 활용되기를 기대한다.
- Barracuda -