Ingress의 개념 및 기본 사용
네임스페이스에 프론트엔드 서버 portal과 백엔드 서버 core 2개의 app이 있다고 하자.
보통 웹 서비스를 구현할 때 쿠버네티스 클러스터 상에서 각 app 서버는 서비스(Service)와 파드(Pod)가 연결되어 있다.
두 서비스는 모두 ClusterIP type이다. 하지만 외부 사용자가 서비스를 이용하기 위해 portal 앱은 외부에서 연결을 해야한다.
여기서 우리는 '외부 오픈을 위해 로드밸런서나 노드포트를 붙여야 하나?' 라고 생각할 수 있다. 하지만 실무에서는 그렇지 않다.
바로 Ingress를 생성하여 트래픽을 받아야 한다.
ingress를 만드는 것은 어떤 rule을 부여하는 것이다.
ingress를 만들 때 어떤 구현체가 만들어지는 것이 아니라 IngressController를 꼭 설치해야 동작하는 오브젝트이다.
service가 kube-proxy와 iptables의 rule대로 동작하는 것처럼 ingressController를 설치하지 않으면 ingress는 동작하지 않는다.
대표적인 IngressController가 바로 그 Nginx 이다.
nginx는 트래픽 관리와 인증서 검증을 수행해준다.
기존에 배운대로 서비스에 트래픽을 날리는 것이 아니라 모든 트래픽은 nginx를 통해서 들어오게 된다.
deployment를 통해 nginx pod를 생성하고 load balancer 타입의 서비스를 nginx에 붙여줘야 한다.
여기서 Nginx가 Ingress에 있는 트래픽 rule로 service를 거쳐 pod로 트래픽을 전달한다고 생각할 수 있다.
하지만 실상은 nginx가 곧바로 rule에 기반하여 pod에 트래픽을 전달하며 그 트래픽을 제어하기 위한 많은 기능들이 존재한다.
# 프론트엔드 ingress
class: nginx
host: portal.com
path: /
service: portal
port: 80
# 백엔드 ingress
class: nginx
host: k8s.core
path: /
service: core
port: 80
이런식으로 ingress를 정의한다고 해보자.
host의 이름으로 호출이 오면 service와 port 속성에 정의된 대로 트래픽을 전달한다.
path는 해당 하위 모든 path에 대하여 해당 portal로 보낸다는 의미이다. 예를 들어 portal.com/* 으로 들어오는 모든 트래픽은 portal 서비스에 연결된 파드로 전달한다는 것이다.
ClusterRole과 ClusterRoleBinding을 통해 nginx가 연결되어 있기 때문에 nginx는 모든 네임스페이스의 ingress를 조회할 수 있다. nginx는 ingress 내용을 확인하여 트래픽을 보낼 서비스 이름을 확인한다.
서비스 이름을 확인하는 이유는 트래픽을 보내기 위해서가 아닌, 서비스 이름으로 알 수 있는 EndpointSlice라는 오브젝트를 찾기 위함인데, 바로 pod ip를 확인하기 위해서이다.
여기서 pod ip를 확인하고 곧바로 파드로 트래픽을 전달한다.
이렇게 외부의 호출 정보에 따라 적절한 파드로 트래픽을 분산시켜주는 역할을 IngressController라는 오브젝트로 수행한다.
이런식으로 구성하면 이제 더이상 iptables가 아닌 nginx가 트래픽을 분산한다. 즉 랜덤 트래픽 분산을 하지 않는다!
위 yaml 정의 예시에서 'class: nginx'는 nginx를 설치하면 IngressClass라는 리소스가 만들어진다. 이 이름이 nginx이다.
Ingress의 class 속성은 결국 해당 이름의 IngressClass가 가지는 rule만 적용하며 다른 class의 ingress는 조회하지 않는다.
class에 이름을 적용하지 않으면 default로 선언된 IngressClass를 적용하는데 잘 쓰지 않는 방식이다.
Nginx 셋업 및 예제
Nginx 설치
curl -O https://get.helm.sh/helm-v3.13.2-linux-amd64.tar.gz
tar -zxvf helm-v3.13.2-linux-amd64.tar.gz
mv linux-amd64/helm /usr/bin/helm
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm pull ingress-nginx/ingress-nginx --version 4.10.0
# 압축 해제
tar -xf ingress-nginx-4.10.0.tgz
# 배포
cd ingress-nginx
curl -O https://raw.githubusercontent.com/k8s-1pro/install/main/ground/cicd-server/nginx/helm/ingress-nginx/values-dev.yaml
helm upgrade ingress-nginx . -f ./values-dev.yaml -n ingress-nginx --install --create-namespace
Nginx 구성 확인
설치가 완료되면 ingress-nginx 네임스페이스와 하위 오브젝트들이 잘 생성되었는지 확인하자.
vagrant@personal:~$ k get ns ingress-nginx
NAME STATUS AGE
ingress-nginx Active 23h
vagrant@personal:~$ k get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller LoadBalancer 10.101.247.157 <pending> 80:31080/TCP,443:31443/TCP 23h
ingress-nginx-controller-admission ClusterIP 10.97.65.147 <none> 443/TCP 23h
vagrant@personal:~$ k get deploy -n ingress-nginx
NAME READY UP-TO-DATE AVAILABLE AGE
ingress-nginx-controller 1/1 1 1 23h
vagrant@personal:~$ k get pod -n ingress-nginx
NAME READY STATUS RESTARTS AGE
ingress-nginx-controller-6dc8c8fdf4-9g9l5 1/1 Running 1 (8h ago) 23h
그 후 nginx라는 IngressClass도 잘 생성되었는지 확인하자.
vagrant@personal:~$ k get ingressClasses nginx -o yaml
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
annotations:
ingressclass.kubernetes.io/is-default-class: "true"
spec:
controller: k8s.io/ingress-nginx
nginx 파드에 nginx ingressClass가 주입되었는지 확인
vagrant@personal:~$ k get pod -n ingress-nginx -o yaml
apiVersion: v1
items:
- apiVersion: v1
spec:
containers:
- args:
- /nginx-ingress-controller
- --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller
- --election-id=ingress-nginx-leader
- --controller-class=k8s.io/ingress-nginx
- --ingress-class=nginx
nginx ingress-controller의 CluterRole 권한도 확인해보면, endpointslices를 조회하는 권한이 존재하는 것을 확인할 수 있다.
vagrant@personal:~$ k get clusterrole ingress-nginx -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
rules:
- apiGroups:
- networking.k8s.io
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- list
- watch
- get
ClusterRoleBinding으로 ClusterRole과 nginx ServiceAccount가 연결된 것도 확인할 수 있다.
vagrant@personal:~$ k get clusterrolebindings ingress-nginx -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ingress-nginx
subjects:
- kind: ServiceAccount
name: ingress-nginx
namespace: ingress-nginx
도메인 분리 테스트
- /portal, core를 위한 pod, service, ingress를 생성한다.
- ingress 내용을 보고 ingress에 연결된 service 이름으로 endpoint slice를 찾아서 pod의 ip 확인하기.
- 내부 서버에서 API 콜하기 k8s.core, etc/hosts에 ip domain을 등록해서 전달이 되는지 확인
실습 파일 배포
kubectl apply -f https://raw.githubusercontent.com/k8s-1pro/kubernetes-anotherclass-sprint3/main/3222/deploy/k8s/namespace.yaml
kubectl apply -f https://raw.githubusercontent.com/k8s-1pro/kubernetes-anotherclass-sprint3/main/3222/deploy/k8s/secret.yaml
kubectl apply -f https://raw.githubusercontent.com/k8s-1pro/kubernetes-anotherclass-sprint3/main/3222/deploy/k8s/configmap.yaml
kubectl apply -f https://raw.githubusercontent.com/k8s-1pro/kubernetes-anotherclass-sprint3/main/3222/deploy/k8s/core/ingress.yaml
kubectl apply -f https://raw.githubusercontent.com/k8s-1pro/kubernetes-anotherclass-sprint3/main/3222/deploy/k8s/core/service.yaml
kubectl apply -f https://raw.githubusercontent.com/k8s-1pro/kubernetes-anotherclass-sprint3/main/3222/deploy/k8s/core/deployment.yaml
kubectl apply -f https://raw.githubusercontent.com/k8s-1pro/kubernetes-anotherclass-sprint3/main/3222/deploy/k8s/portal/ingress.yaml
kubectl apply -f https://raw.githubusercontent.com/k8s-1pro/kubernetes-anotherclass-sprint3/main/3222/deploy/k8s/portal/service.yaml
kubectl apply -f https://raw.githubusercontent.com/k8s-1pro/kubernetes-anotherclass-sprint3/main/3222/deploy/k8s/portal/deployment.yaml
Ingree 구성 확인
vagrant@personal:~$ k get svc -n anotherclass-322 portal-3222
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
portal-3222 ClusterIP 10.100.74.217 <none> 80/TCP 2m30s
vagrant@personal:~$ k get ingress -n anotherclass-322 portal-3222 -o yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"networking.k8s.io/v1","kind":"Ingress","metadata":{"annotations":{},"name":"portal-3222","namespace":"anotherclass-322"},"spec":{"ingressClassName":"nginx","rules":[{"host":"portal.com","http":{"paths":[{"backend":{"service":{"name":"portal-3222","port":{"number":80}}},"path":"/","pathType":"Prefix"}]}}]}}
creationTimestamp: "2025-02-22T13:24:45Z"
generation: 1
name: portal-3222
namespace: anotherclass-322
resourceVersion: "202402"
uid: 6dd35ef4-9ca1-4917-aec4-659ff371e127
spec:
ingressClassName: nginx
rules:
- host: portal.com
http:
paths:
- backend:
service:
name: portal-3222
port:
number: 80
path: /
pathType: Prefix
status:
loadBalancer: {}
vagrant@personal:~$ k get endpoints -n anotherclass-322 portal-3222 -o yaml
apiVersion: v1
kind: Endpoints
subsets:
- addresses:
- ip: 192.168.13.246
nodeName: personal
targetRef:
kind: Pod
name: portal-3222-5c4fbcfd85-4v9cl
namespace: anotherclass-322
uid: a0821d64-4d55-4c8e-94ba-460895b40d16
- ip: 192.168.13.247
nodeName: personal
targetRef:
kind: Pod
name: portal-3222-5c4fbcfd85-wrsmc
namespace: anotherclass-322
uid: 1c61f561-0744-4a7e-acfd-1823b17c6065
ports:
- port: 8080
protocol: TCP
vagrant@personal:~$ k get endpointslices -n anotherclass-322 portal-3222-cxch2 -o yaml
kind: EndpointSlice
metadata:
annotations:
endpoints.kubernetes.io/last-change-trigger-time: "2025-02-22T13:25:08Z"
creationTimestamp: "2025-02-22T13:24:46Z"
generateName: portal-3222-
generation: 5
labels:
component: frontend-server
endpointslice.kubernetes.io/managed-by: endpointslice-controller.k8s.io
instance: portal-3222
kubernetes.io/service-name: portal-3222
managed-by: kubectl
name: portal
part-of: k8s-anotherclass
version: 3.0.0
name: portal-3222-cxch2
namespace: anotherclass-322
ownerReferences:
- apiVersion: v1
blockOwnerDeletion: true
controller: true
kind: Service
name: portal-3222
uid: 1e476f60-664b-44af-b9a8-fb984af73640
resourceVersion: "202532"
uid: 69be5cc4-bd6d-41d6-95d9-04eb4dda5299
ports:
- name: ""
port: 8080
protocol: TCP
endpointslices는 service이름 뒤에 난수가 붙어져서 만들어진 것을 확인할 수 있다. (portal-3222-cxch2)
또한 ownerReferences 속성을 통해 어떤 service가 주인인지 확인할 수 있다. (kind: Service / name: portal-3222)
/etc/hosts 파일에 도메인을 추가하고 API를 날려보면 해당 portal 파드 이름을 확인할 수 있다.
192.168.56.33 portal.com
# http://portal.com:31080/hostname (크롬으로 요청)
portal-3222-5c4fbcfd85-4v9cl
nginx 로그를 확인해 보면 브라우저 접속 로그가 뜬다.
vagrant@personal:~$ k logs -n ingress-nginx ingress-nginx-controller-6dc8c8fdf4-9g9l5 --tail 10
10.0.2.15 - - [22/Feb/2025:14:26:27 +0000] "GET /hostname HTTP/1.1" 200 25 "-" "curl/7.68.0" 86 0.012 [anotherclass-322-core-3222-80] [] 192.168.13.244:8080 25 0.012 200 bac09a3945d97605f81fb7a56e286a64
10.0.2.15 - - [22/Feb/2025:14:26:38 +0000] "GET /hostname HTTP/1.1" 200 28 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36" 477 0.048 [anotherclass-322-portal-3222-80] [] 192.168.13.246:8080 28 0.048 200 1f585c591fb78793cab29b2d4b50aead
10.0.2.15 - - [22/Feb/2025:14:26:38 +0000] "GET /favicon.ico HTTP/1.1" 404 111 "http://portal.com:31080/hostname" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36" 422 0.071 [anotherclass-322-portal-3222-80] [] 192.168.13.247:8080 111 0.071 404 3129405be89ef10f3da922feacccd5e
Nginx 사용 시 마주하게 되는 상황들과 해결 방법.
사용자는 포탈에 접속하기 위해 http로 호출하면 80번 포트가 호출된다. port 80은 보내는 통신에 대해서 암호화가 되지 않는다.
https로 통신하면 기본 443 포트로 접속하고, 통신에 TLS 암호화가 적용되어 트래픽을 까봐도 내용을 알 수 없다.
이런 https 암호화를 적용하기 위해서는 인증서가 필요하다.
공인 인증서의 경우 브라우저에서 '신뢰된 사이트' 표시가 뜨고 비용이 필요하다. 사설 인증서는 '신뢰되지 않는 기관'이라는 주의 문구가 뜨며 테스트용으로 많이 사용한다.
이런 암호화를 TLS 암호화라고 하며 기업 보안의 필수이며, 통신에 무조건 TLS를 적용한다고 가정한다.
여기서 고려해야할 점이 있다. 인프라 환경에서 어디에 TLS를 적용할까?
app에 적용하게 되면 app이 많아지거나 업데이트가 이루어질 때 인증서 관리가 불편하다. 따라서 다음 2가지 방식을 사용한다.
- Ingress에 tls type의 secret을 등록하여 service에 연결된 모든 파드에 동일한 인증서 적용
- nginx 자체에 인증서를 적용하여 nginx를 지나는 모든 통신에 인증서를 공통 적용. 이 때 TLS가 적용된 ingress를 사용 시에는 해당 ingress의 인증서를 사용한다.
공인인증서를 적용할 때는 DNS 이름이 중요하다. 외부에서 접속하는 도메인 이름과 서비스에 대한 도메인 이름이 일치해야 한다.
사설인증서는 nginx의 인증서를 사용하고, 공인인증서는 인증서에 dns 이름을 어떻게 하냐에 따라 설치 위치가 달라진다.
로드밸런서에도 인증서를 심을 수 있다. 이 때 외부 사용자와는 암호화가 적용되지만 내부 통신에는 암호화가 미적용된다. 내부망에 들어오니까 상관없을 수 있다. 하지만 클라우드 서비스를 사용하여 서로 다른 대륙이나 지역간에 워커 노드가 위치할 수 있다.
이러한 경우 내부 통신이더라도 트래픽이 외부망을 통해 나가기 때문에 주의가 필요하다.
또한 인증서를 상위단(loadbalancer)에 적용하고 하위(nginx, ingress)에도 적용하면 성능이 느려지기 때문에, 전체적인 구조를 고려하여 적용하는게 필요하다.
portal(프론트엔드 앱)에서 path를 분기하여 각 app을 호출한다. 그리고 클러스터 내부의 파드에서도 다른 app을 호출하기 위해 nginx를 사용한다. path에 따라 서비스를 다르게 줄 수있다. ingress의 path를 서비스 별로 만들면 portal에서는 path에 따라 각 app으로 호출이 된다.
path: /list-customer
service: cust
path: /save-data
service: core
이런식으로 /list-customer로 request가 오면 cust app에 보내주고, /save-data로 request가 오면 core app으로 전달한다.
하지만 두 app에 동일한 이름의 API가 있으면 문제가 된다. 만약 cust, core 두 app에 모두 /hostname 이라는 api가 있다고 가정해보자. 그렇다면 위처럼 ingress를 구성했을 때 /hostname을 어떤 service로 보내주어야 하는지 구분할 수 없다. 따라서 위 예시처럼 ingress를 구성하지 않는다.
먼저 portal은 서비스를 호출해야 하니까 service, namespace를 붙여서 도메인 이름을 생성한다. (http://ingress-nginx-controller.ingress-nginx). 그 후 coreDNS에 질의하여 도메인 이름으로 서비스를 호출한다.
그 다음 도메인 뒤에 호출할 api를 생성하는데, 이 때 path의 가장 앞에 호출할 서비스 이름을 붙인다. (/cust/hostname, /core/hostname).
이후 아래와 같이 ingress를 설정하면 된다.
rewrite-target: /$2
path: /core(/|$)(.*)
service: core
port:80
이런식으로 구성하게 되면 path에는 portal에서 분기한 path로 시작하여 뒤에 정규식이 붙는다.
/core 이후의 어떠한 path도 인식하게 되며, rewrite-target을 통해 '/' 문자를 기준으로 2번째 이후 path를 실제 api path로 사용한다. 즉 /core/hostname 이라는 api는 core라는 서비스로 매칭이 되지만, 실제 core에 보내지는 api는 /hostname으로 보내진다는 의미이다.
Nginx의 유용한 설정
http를 https로 자동 리다이렉션
사용자들은 http, https를 구분하지 않고 api를 요청한다. 이 때 http는 80번 포트가 막혀있기 때문에 접속이 되지 않지만, 자동으로 https로 리다이렉션 해줄 수 있다.
먼저 https 적용을 위해 TLS 인증서를 적용해보자.
portal용 TLS secret 생성
# 개인키 생성
vagrant@personal:~/tls$ openssl genrsa -out tls.key 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
................................................+++++
...................+++++
e is 65537 (0x010001)
# 인증서 서명 요청 (CSR) 생성
vagrant@personal:~/tls$ openssl req -new -x509 -key tls.key -out tls.crt -days 3650 -subj "/CN=portal.com"
# TLS 타입의 secret 생성
vagrant@personal:~/tls$ k create secret tls portal-3222-tls -n anotherclass-322 --cert=tls.crt --key=tls.key
secret/portal-3222-tls created
# 확인
vagrant@personal:~/tls$ k get secret portal-3222-tls -n anotherclass-322 -o yaml
apiVersion: v1
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURDekNDQWZPZ0F3SUJBZ0lVYm1BbFNBZHNXWjNMNWY5YVlNZWRhQ2ZmZlJRd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0ZURVRNQkVHQTFVRUF3d0tjRzl5ZEdGc0xtTnZiVEFlRncweU5UQXlNak13TVRJd016ZGFGdzB6TlRBeQpNakV3TVRJd016ZGFNQlV4RXpBUkJnTlZCQU1NQ25CdmNuUmhiQzVqYjIwd2dnRWlNQTBHQ1NxR1NJYjNEUUVCCkFRVUFBNElCRHdBd2dnRUtBb0lCQVFEU0JOU1lQSlg1Ynd2WUZ1YytXbUF4dzkvRnlnZGs2aUlET3orUCtBZHoKRTZmNDBCRXVzMFpNK3VYOCtWSnhwaXBLNWxIaDRlaTEwemdHS1hSM0lSbmNKMWlpaURpM3RGVzRlZEFHcmtOaApZR3Zhc2VxK3RKRHJ6dnA2OUJQN2ZIOWZOakVzam5hcHhWTlFHcWRvc21MVnl0OS9GcVl4U3FNVW1xc0d3RjhsCkJUSEs0OW50MjFIVzFPTklhWHZTVjZHWGR3MitRR3hWcWZ3OEU3WThRdGhsSEhuZG8vaWx1L0s0cmxPd2Jxd1YKa2NUVUtoZ244WUNHZkJVU3BMSG1Va0Y3MWhCWXowVTQrcGllTHVOTWVGeEdZYWl0SFM3Z3g4amNCaGh4ekpWVQo3aGpPc0k1dmJ4MSswbmxpMzlRRVUyTHJhNWRvR3FzNVphcW93OE9IcGxSaEFnTUJBQUdqVXpCUk1CMEdBMVVkCkRnUVdCQlN6MkhML0pvZ1BTTVdkdTNyVlZINk1GL1NNcWpBZkJnTlZIU01FR0RBV2dCU3oySEwvSm9nUFNNV2QKdTNyVlZINk1GL1NNcWpBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFCbwpGSzd5eXM5UGhJQ0JGTlozQlZJRHV5ZisyRjZVbkpPQWVCL2lzd3BZbDJVSlErMFJqN2ZzTjhHaXluRG5aMzF4CjVNM1A5KzM3NmtadjdkOXhKMnFSQS94T1BERHZnbk4rQTF4ZW5sUlM1dGNMWEtYaGFmcGVGLyszT0lKU1hxczgKWWlDeDk1ZnRjckszRVhBWEd2MmxNUm1YS3RkUGdWRllCL1NzU0hWdWcwTXlFcTNCd1gyalBTVE9TRlVkalByRQptRjRhc3AwaUR2NlVLRW9IL1BVdWg5VDNLQ2VSbjdkSllFK0lRTHJBUDlJZ3dRSUR5OTA0VGVVSHNWSjhzSDFxCnpPb3Y0bk4ycDhKZlljdHEyQ2VUdUZvcjBnTU92Q2FkeDhsdmh2Z3pldzdRK3Z1U2xkVmZMSG1ObzhadkxES3MKQmRzUldtdFF2RStvbFR4QzJxRHAKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBMGdUVW1EeVYrVzhMMkJiblBscGdNY1BmeGNvSFpPb2lBenMvai9nSGN4T24rTkFSCkxyTkdUUHJsL1BsU2NhWXFTdVpSNGVIb3RkTTRCaWwwZHlFWjNDZFlvb2c0dDdSVnVIblFCcTVEWVdCcjJySHEKdnJTUTY4NzZldlFUKzN4L1h6WXhMSTUycWNWVFVCcW5hTEppMWNyZmZ4YW1NVXFqRkpxckJzQmZKUVV4eXVQWgo3ZHRSMXRUalNHbDcwbGVobDNjTnZrQnNWYW44UEJPMlBFTFlaUng1M2FQNHBidnl1SzVUc0c2c0ZaSEUxQ29ZCkovR0FobndWRXFTeDVsSkJlOVlRV005Rk9QcVluaTdqVEhoY1JtR29yUjB1NE1mSTNBWVljY3lWVk80WXpyQ08KYjI4ZGZ0SjVZdC9VQkZOaTYydVhhQnFyT1dXcXFNUERoNlpVWVFJREFRQUJBb0lCQUZibTVNa3htS1VqeS9UWQpWVXM1dmtBWldzak85Z3hOelZFay9RSW5rWDJPanB5b0M5UVZIbXZRRGNhRnJCV1d1ajFQM0dYcFFvK3FISzZBCkRNOFdoVXJSaFIyTDNGdzNaSmoyM3B0RkZHQ0FzMkJFNm11Szc4U0RXcVh4eFNyMHN3ZkV0c2hIRXRiZzdiNWgKcVEvcmNIWWlnaVJtVE5Kc25kcHl2WDRIcjlieTlVeDU3OGZZeWJ5TDJFNlBVMTQ1VEtQSkZnNFdUNkxwSTBCWgpNbU5iT1NWTDBXRTFPa1BlMC9hKzRxeEVVTmNIc0hydjNDZHhVOFk5UGh0RFRybk9lYWN5ZXlOaHVzZUlzeGN0Cjl5cUUvQ09WdXp4cjhqOUVFaXR5c2tFcXUvQzRKOFBmSHV5WFBKd3VneElCWmdpU1NyTXBqVUpVUDk0V1VadjkKUXBDdXY2RUNnWUVBNy9BMklwUzFMOFhFMlQzdWRRMjBGZUk0V3dFT2lKNEFIQ0g5ZGwwdDVuSEprQXJSa2NsdgoxdmFSekNwNVNqdWFFYmFuQ3FkS0hWM1pQR1ZISFFwOE9jN1grSHBQbTExZjBFVG9velVRbWJMS1ZtS25WKzQrCkpkMHR6bC84a200RUpYTS9HRjIySzZ0VlBEMmxGSGQ1cURvYzY3a01EZk5jWlN6MWZRWUx3UzBDZ1lFQTRCUGsKd0JLelhOM2M0bEFLSkJNb0JZMWFaYmJ4SnF6RElzcmhFMEJ1M2pKV2RONUxvMXlzalNPdGRoRWQ4aVJaL3ZPKwp3dVREVE03eHcyNlRJR1REdld3SzVCbXBiUEFFOFFTeTdFYkxOMDhZRkE4NG4zY1lJZjFyc1JuNmUxMUxHdFlCCnVCMmliWVIyeFJsQlBpVFZ4WERVQk83TlY4Q3B1R2Z2Y1hEaDJJVUNnWUE0cGYwU0F3d1lJRmhPZGhuRGx4MnEKZ1FPUE1OdXVrNmN3L1RGcmJhcDFtdTNjelVFWGEzaTFSdmU3UFZmdmw4elRXODFxYlg3SkdpdzgzeWlBQ2NhMAptMHBRUndoZWhSZ25BdTN1QW94TnpYR0tIYmFtd3YvM1RwWVBBSWlRUmR0Q3dFZm9SOC9IMWFiRkYwVFBaVG5hCjV3V0cvZVNEc3E1ZzBOR1EyQW1VYlFLQmdRQ3haWTUzc1ZabWN3N243RTNhK1g5NXhjcUhOQ24rWUUrc1A4bW8Kb0I0bHN0eFhjWldZSTBwSUFyTFZBZ0FmTDBCQVkxL21rNDA2UmF1SStRVnBZOXpESGNJUmkyOWlXekxPVE5ZSgowSklabTNpSWlQUmU2cjRrU3FqemNJbXBHTmJZekF4WVlyMnd6bW5vNkhHS2JVY0FKSzcxcU1qUWJDcnhYdmRsCmErU2dzUUtCZ1FDdlhGcGdYMVN2dE1pV2xjN21oa0FzZkhraTVEWFJDVGRqTmROdTlPZEh4QnZWcCtkVEZNVGsKOVJaVDRqUE1zMWhJcVowNGFiMEI4Y2pvdkNzdG1nZ0c3ZVN2THhValRPNE5NUnNiaGVray8xb2FtRVczeXpYbApIMjZZUkdYM1A0MmpwMjJDQWE0Rnd2bzNXTnFvdUpTaThMbEFYcGh5T0hnSGRvMTQ3aHpWbnc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
kind: Secret
metadata:
creationTimestamp: "2025-02-23T01:20:52Z"
name: portal-3222-tls
namespace: anotherclass-322
resourceVersion: "214761"
uid: 61a6982b-5dd2-4304-b9c5-a4d0c52e61d7
type: kubernetes.io/tls
그 다음 portal ingress에 tls를 추가한다.
spec:
ingressClassName: nginx
rules:
- host: portal.com
http:
paths:
- backend:
service:
name: portal-3222
port:
number: 80
path: /
pathType: Prefix
tls: # 추가
- hosts: # 추가
- portal.com # 추가
secretName: portal-3222-tls # 추가
status:
loadBalancer: {}
그러면 https 접속 시 TLS 인증서가 적용된다. http로 접속하게 되면 아래와 같이 400 Bad Request가 발생한다.
이를 해결하기 위해 ingress의 annotation에 configuration-snippet 옵션을 추가해주면 된다.
http로 들어오는 경우 자동으로 31443 포트로 리다이렉션 하여 https 통신을 수행한다.
kubectl edit -n anotherclass-322 ingress portal-3222
--
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
if ($scheme = http) {
return 301 https://$host:31443$request_uri;
}
-
만약 포트 번호를 제외하고 요청을 날리면 잘 되는데, 이는 ingress에 TLS가 설정되어있을 경우 아래 설정 기본값이 적용되기 때문이다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
클러스터 내부에서 호출 시 트래픽 분리
portal에서 nginx로 호출하면 /core/hostname, /cust/hostname 경로에 따라 적절한 pod에 트래픽을 전달하는 방법이다.
먼저 기존의 ingress는 아래와 같이 설정되어 있다.
kind: Ingress
spec:
rules:
- http:
paths:
- path: /core
pathType: Prefix
backend:
service:
name: core-3222
port:
number: 80
- Prefix: path의 시작 부분만 일치 하면, Service로 트래픽 보냄 (/core로 설정된 경우, /core, /coretest, /core/hostname가 모두 매칭)
- Exact: path에 지정한 URL 경로가 정확히 일치해야, Service로 트래픽 보냄
- ImplementationSpecific: Ingress 컨트롤러(nginx)의 구현에 따라, Service로 트래픽 보냄
core ingress에 rewrite-target을 적용시켜보자.
vagrant@personal:~/tls$ k get -n anotherclass-322 ingress core-3222-internal -o yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
nginx.ingress.kubernetes.io/use-regex: "true"
spec:
ingressClassName: nginx
rules:
- http:
paths:
- backend:
service:
name: core-3222
port:
number: 80
path: /core(/|$)(.*)
pathType: ImplementationSpecific
portal pod 내부에서 API 호출 테스트
vagrant@personal:~/tls$ k exec -n anotherclass-322 portal-3222-5c4fbcfd85-4v9cl -it -- /bin/sh
sh-4.4# curl ingress-nginx-controller.ingress-nginx/core/hostname
core-3222-8c8d6585d-8llmf
sh-4.4# curl ingress-nginx-controller.ingress-nginx/cust/hostname
cust-3222-77df9457d-cgjvg
curl 호출 시 ingress-nginx-controller.ingress-nginx로 도메인을 줬음. 이는 <service-name>.<namespace>로 도메인을 구성한 것.
vagrant@personal:~/tls$ k get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller LoadBalancer 10.101.247.157 <pending> 80:31080/TCP,443:31443/TCP 36h
ingress-nginx-controller-admission ClusterIP 10.97.65.147 <none> 443/TCP 36h
Portal Ingress에 Rule 추가시 기존 Rule 이상 유무를 확인해보자
vagrant@personal:~/tls$ k get ingress -n anotherclass-322
NAME CLASS HOSTS ADDRESS PORTS AGE
core-3222 nginx k8s.core 80 12h
core-3222-internal nginx * 80 12m
cust-3222-internal nginx * 80 12m
portal-3222 nginx portal.com 80, 443 12h
core-3222-internal은 호스트 이름을 주지 않아서 asterisk로 host가 설정되어 있음.
따라서 앞에 어떤 호스트가 들어와도 해당 rule에 걸린다는 뜻이다.
그렇다면 만약 host 이름을 k8s.core로 줬을 때 core-322에 걸릴까 core-3222-internal에 걸릴까?
https://portal.com:31443/hostname → 잘 됨
https://portal.com:31443/core/hostname → 안됨
이는 core-3222-internal이 아니라 core-322에 걸린 것이다.
Ingress는 가장 구체적으로 매칭되는 조건 딱 하나만 적용된다.
kubectl delete -n anotherclass-322 ingress core-3222 명령어로 core-3222 ingress를 제거하면 /core/hostname 요청이 정상적으로 처리된다.
'인프라' 카테고리의 다른 글
[쿠버네티스] Nginx 설정 변경하기 - 로드밸런싱 방식, 로그 포맷(log format), 타임 존(timezone) 변경 (0) | 2025.03.11 |
---|---|
[쿠버네티스] 인프라 구성으로 배우는 Kubernetes Service의 거의 모든 기능들 (0) | 2025.03.09 |
[쿠버네티스] Application 개발자가 꼭 알아야하는 Kubernetes Pod 기능 (2) - Pod 종료 시 안정적으로 Application 종료하기 (0) | 2025.03.08 |
[쿠버네티스] Application 개발자가 꼭 알아야하는 Kubernetes Pod 기능 (1) - Pod 정보 조회하기 (1) | 2025.03.08 |
개발자 쿠버네티스 개발/테스트 환경 구축하기 (0) | 2025.03.07 |