티스토리 뷰

728x90
반응형

쿠버네티스 고급 기능 활용

  • 네트워크 7계층에서 가상 호스트를 이용해 서비스 요청을 처리
  • 애플리케이션의 영속적인 데이터를 보관하기 위한 외부 볼륨이 필요
  • 여러 명의 개발자 또는 애플리케이션이 함께 사용하는 쿠버네티스 클러스터에서는 보안을 위해 권한을 관리
  • 특정 포드가 컴퓨팅 자원을 독차지하는 것을 막기 위해 메모리, CPU 사용량의 제한을 위한 체계적인 시스템이 필요




들어가기전에

인그레스는 일반적으로 외부에서 내부로 향하는 것을 지칭하는 단어입니다. 예를 들어 인그레스 트래픽은 외부에서 서버로 유입되는 트래픽을 의미하며, 인그레스 네트워크는 인그레스 트래픽을 처리하기 위한 네트워크를 의미합니다.

인그레스는 외부 요청을 어떻게 처리할 것인지 네트워크 7계층 레벨에서 정의하는 쿠버네티스 오브젝트입니다.

인그레스 오브젝트가 담당할 수 있는 기본적인 기능

  • 외부 요청의 라우팅 : 특정 경로로 들어온 요청을 어떠한 서비스로 전달할지 정의하는 라우팅 규칙을 설정
  • 가상 호스트 기반의 요청 처리 : 같은 IP에 대해 다른 도메인 이름으로 요청이 도착 했을 때, 어떻게 처리할 것인지 정의
  • SSL/TLS 보안 연결 처리 : 여러 개의 서비스로 요청을 라우팅할 때, 보안 연결을 위한 인증서를 쉽게 적용




인그레스를 사용하는 이유

애플리케이션이 3개의 디플로이먼트로 생성돼 있다고 가정해 보겠습니다. 각 디플로이먼트를 외부에 노출해야 한다면 NodePort 또는 LoadBalancer 타입의 서비스 3개를 생성하는 방법을 떠올릴 수 있습니다.

위 방식은 언뜻 보기에는 잘 동작하는 것 같지만, 서비스마다 세부적인 설정을 할 때 추가적인 복잡성이 발생하게 됩니다. SSL/TLS 보안 연결, 접근 도메인 및 클라이언트 상태에 기반한 라우팅 등을 구현하려면 각 서비스와 디플로이먼트에 대해 일일이 설정을 해야 하기 때문입니다.

쿠버네티스가 제공하는 인그레스 오브젝트를 사용하면 URL 엔드포인트를 단 하나만 생성함으로써 이러한 번거로움을 쉽게 해결할 수 있습니다. 3개의 디플로이먼트를 외부로 노출하는 인그레스를 생성하면 다음 그림과 같이 요청이 처리됩니다.

위 그림에서는 3개의 서비스에 대해 3개의 URL이 각각 존재하는 것이 아닌, 인그레스에 접근하기 위한 단 하나의 URL만 존재합니다. 따라서 클라이언트는 인그레스의 URL로만 접근하게 되며, 해당 요청은 인그레스에서 정의한 규칙에 따라 처리된 뒤 적절한 디플로이먼트의 포드로 전달됩니다.

이 과정에서 중요한 점은 라우팅 정의나 보안 연결 등과 같은 세부 설정은 서비스와 디플로이먼트가 아닌 인그레스에 의해 수행된다는 것입니다. 각 디플로이먼트에 대해 일일이 설정을 적용할 필요 없이 하나의 설정 지점에서 처리 규칙을 정의하기만 하면 됩니다. 즉, 외부 요청에 대한 처리 규칙을 쿠버네티스 자체의 기능으로 편리하게 관리할 수 있다는 것이 인그레스의 핵심입니다.




인그레스의 구조

인그레스는 쿠버네티스에서 ingress라는 이름으로 사용할 수 있으며, kubectl get ingress 명령어로 인그레스의 목록을 확인할 수 있습니다.

$ kubectl get ingress
$ kubectl get ing
# vi ingress-example.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-example
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: "nginx"
spec: 
  rules:
  - host: alicek106.example.com                   # [1]
    http:
      paths:
      - path: /echo-hostname                     # [2]
        pathType: Prefix
        backend:
          service:
            name: hostname-service
            port:
              number: 80
  • host: 해당 도메인 이름으로 접근하는 요청에 대해서 처리 규칙을 적용합니다. 위 예시에서는 alicek106.example.com이라는 도메인으로 접근하는 요청만 처리하지만, 여러 개의 host를 정의해 사용할 수도 있습니다.
  • path: 해당 경로에 들어온 요청을 어느 서비스로 전달할 것인지 정의합니다. 위 예시에서는 /echo-hostname이라는 경로와 요청을 backend에 정의된 서비스로 전달합니다. 여러 개의 path를 정의해 경로를 처리할 수도 있습니다.
  • service
    • name, port: path로 들어온 요청이 전달될 서비스와 포트입니다. 즉, 위 예시에서는 /echo-hostname이라는 경로로 들어온 요청을 hostname-service 서비스의 80 포트로 전달합니다.
  • annotation : 항목을 통해 인그레스의 추가적인 기능을 사용할 수 있습니다. (추 후 자세히..)
$ kubectl apply -f ingress-example.yaml
$ kubectl get ing

ingress-example이라는 이름의 인그레스를 생성했지만, 이것만으로는 아무 일도 일어나지 않습니다. 인그레스는 단지 요청을 처리하는 규칙을 정의하는 선언적인 오브젝트일 뿐, 외부 요청을 받아들일 수 있는 실제 서버가 아니기 때문입니다. 인그레스는 인그레스 컨트롤러(Ingress Controller)라고 하는 특수한 서버에 적용해야만 그 규칙을 사용할 수 있습니다.

대표적인 인그레스 컨트롤러 서버로는 쿠버네티스 커뮤니티에서 활발히 사용되고 있는 Nginx 웹 서버 인그레스 컨트롤러가 있습니다. 그 외에도 Kong이라는 API 게이트웨이나 GKE 등의 클라우드 플랫폼에서 제공되는 인그레스 컨트롤러가 있습니다. 이번 실습에서는 Nginx 인그레스 컨트롤러를 사용해보겠습니다.

Minikube에서는 ingress 관련 설정을 enable 해주면, Nginx 인그레스 컨트롤러 관련 쿠버네티스 리소스를 한 번에 생성합니다.

$ minikube addons enable ingress

깃허브 레포 : https://github.com/kubernetes/ingress-nginx
설치 매뉴얼 : https://kubernetes.github.io/ingress-nginx/deploy/#minikube

Nginx 인그레스 컨트롤러를 설치하기 위해 다양한 쿠버네티스 리소스를 한 번에 생성합니다. 우선 ingress-nginx라는 네임스페이스에 Nginx 웹 서버 디플로이먼트를 생성하고, 그와 관련된 설정들을 컨피그맵으로 생성하는 것을 알 수 있습니다.

$ kubectl get all -n ingress-nginx

로컬 환경에서 Window, WSL, Docker Desktop, Minikube를 사용할 경우, minikube 터널링과 dns 설정을 해줘야합니다.

$ minikube tunnel
# root 비번 입력
$ sudo vi /etc/hosts
...
# reboot시에도 적용을 위해 아래 2줄 주석 풀기
[network]
generateHosts = false
...
...
127.0.0.1 alicek106.example.com

이로써 인그레스, Nginx 인그레스 컨트롤러 및 Nginx 포드에 접근하기 위한 서비스의 준비가 완료됐습니다. 하지만 아직 인그레스의 종착점이 될 테스트용 디플로이먼트와 서비스를 생성하지 않았으므로(echo-hostname) 이를 생성해 최종적으로 인그레스의 동작 여부를 확인해 보겠습니다. Nginx 인그레스 컨트롤러로 들어오는 요청은 이 디플로이먼트의 포드들로 분산될 것입니다.

# vi hostname-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hostname-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: webserver
  template:
    metadata:
      name: my-webserver
      labels:
        app: webserver

    spec:
      containers:
      - name: my-webserver
        image: alicek106/ingress-annotation-test:0.0
        ports:
        - containerPort: 5000
          name: flask-port
# vi hostname-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: hostanme-service
spec:
  ports:
    - name: web-port
      port: 80
      targetPort: flask-port
  selector:
    app: webserver
  type: ClusterIP
$ kubectl apply -f hostname-deployment.yaml
$ kubectl apply -f hostname-service.yaml

인그레스 컨트롤러에 의해 요청이 최종적으로 도착할 디플로이먼트의 서비스는 어떤 타입이든지 상관은 없습니다. 다만 굳이 외부에 서비스를 노출할 필요가 없다면 ClusterIP 타입을 사용하는 것이 좋습니다. 위의 예제에서는 hostname-service라는 이름의 서비스를 ClusterIP타입으로 생성했습니다.

워커노드의 도커 이미지에 웹 정적 페이지가 없기 때문에 curl로 테스트를 해야합니다.

curl alicek106.example.com/echo-hostname




인그레스 컨트롤러의 동작 원리 이해

인그레스를 사용하는 방법을 순서대로 정리해보면,

  1. 공식 깃허브에서 제공되는 야믈 파일로 Nginx 인그레스 컨트롤러를 생성합니다.
  2. Nginx 인그레스 컨트롤러를 외부로 노출하기 위한 서비스를 생성합니다. 미니쿠버의 경우 minikube addons enable ingress로 1번과 2번이 전부 다 생성됩니다.
  3. 요청 처리 규칙을 정의하는 인그레스 오브젝트를 생성합니다. ingress-example.yaml
  4. Nginx 인그레스 컨트롤러롤 들어온 요청은 인그레스 규칙에 따라 적절한 서비스로 전달됩니다. hostname-deployment.yaml, hostname-service.yaml

위 과정중 3번에서 인그레스를 생성하면 인그레스 컨트롤러는 자동으로 인그레스를 로드해 Nginx 웹 서버에 적용합니다. 이를 위해 Nginx 인그레스 컨트롤러는 항상 인그레스 리소스의 상태를 지켜 보고 있으며, 기본적으로 모든 네임스페이스의 인그레스 리소스를 읽어와 규칙을 적용합니다.

참고

쿠버네티스 API에는 특정 오브젝트의 상태가 변화하는 것을 확인할 수 있는 Watch라는 API가 있으며, 인그레스 컨트롤러 또한 인그레스 리소스에 대해 Watch API를 사용합니다. Watch는 리소스에 생성, 삭제, 수정 등의 이벤트가 발생했을 때 이를 알려주는 기능으로 kubectl get 명령어에서도 -w 옵션을 붙여 사용할 수 있습니다.

$ kubectl get po -w

특정 경로와 호스트 이름으로 들어온 요청은 인그레스에 정의된 규칙에 따라 서비스로 전달됩니다. 이전에 생성했던 테스트용 인그레스에서는 /echo-hostname이라는 경로로 들어온 요청을 hostname-service라는 서비스의 80 포트로 전달했습니다.

하지만 요청이 실제로 hostname-service라는 서비스로 전달되는 것은 아니며, Nginx 인그레스 컨트롤러는 서비스에 의해 성성된 엔드포인트로 요청을 직접 전달합니다. 즉 서비스의 ClusterIP가 아닌 앤드포인트의 실제 종착 지점들로 요청이 전달되는 셈입니다. 이러한 동작을 쿠버네티스에서는 바이패스(bypass)라고 부릅니다.

$ kubectl get ep
여기서 나오는 hostname-service 앤드포인트랑

$ kubectl get po -o wide
여기 포드 ip들이 일치함




인그레스의 세부 기능 : annotation을 이용한 설정

위에서 사용했던 ingress-example.yaml파일을 보겠습니다.

# vi ingress-example.yaml 
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-example
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - host: alicek106.example.com                   # [1]
    http:
      paths:
      - path: /echo-hostname                     # [2]
        pathType: Prefix
        backend:
          service:
            name: hostname-service
            port:
              number: 80

kubernetes.io/ingress.class는 해당 인그레스 규칙을 어떤 인그레스 컨트롤러에 적용할 것인지를 의미합니다. Nginx 외에도 Kong이나 GKE 등 여러 가지 중 하나를 선택해 사용할 수 있습니다.

nginx.ingress.kubernetes.io/rewrite-target는 Nginx 인그레스 컨트롤러에서만 사용할 수 있는 기능입니다. 이 주석은 인그레스에 정의된 경로로 들어오는 요청을 rewrite-target에 설정된 경로로 전달합니다. 예를 들어, Nginx 인그레스 컨트롤러로 /echo-hostname으로 접근하면 hostname-service에는 / 경로로 전달됩니다.

단, rewrite-target은 /echo-hostname이라는 경로로 시작하는 모든 요청을 hostname-service의 /로 전달합니다. 예를 들어 /echo-hostname/alice/bob이라는 경로로 요청을 보내도 똑같이 /로 전달합니다.

사실 rewrite-target은 Nginx의 캡쳐 그룹과 함께 사용할 때 유용한 기능입니다. 캡처 그룹이란 정규 표현식의 형태로 요청 경로 등의 값을 변수로서 사용할 수 있는 방법입니다.

아래처럼 인그래스를 변경해봅시다.

# vi ingress-example.yaml 
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-example
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2  # path의 (.*)에서 획득한 경로를 전달.
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - host: alicek106.example.com                  
    http:
      paths:
      - path: /echo-hostname(/|$)(.*)   # (.*)을 통해 경로를 얻습니다. 
        pathType: Prefix
        backend:
          service:
            name: hostname-service
            port:
              number: 80

path 항목에서 (.*)이라는 Nginx의 정규 표현식을 통해 /echo-hostname/뒤에 오는 경로를 얻은 뒤, 이 값을 rewrite-target에서 사용합니다. 즉, /echo-hostname/으로 접근하면 이전과 동일하게 /로 전달되지만, /echo-hostname/color는 /color로, /echo-hostname/color/red는 /color/red로 전달됩니다. 즉, 요청 경로를 다시 쓰는(rewrite) 것입니다.

그 외에도 루트 경로로 접근했을 때 특정 path로 리다이렉트하는 app-root 주석이나 SSL 리다이렉트를 위한 ssl-redirect 주석 등을 사용할 수 있습니다. Nginx 인그레스 컨트롤러에서 사용할 수 있는 주석은 공식 사이트에서 확인할 수 있습니다. 이런 주석들은 Nginx 컨트롤러에서만 사용할 수 있습니다.

주석을 사용해도 별도의 기능을 사용할 수 있지만, 필요하다면 Nginx 인그레스 컨트롤러와 함께 생성된 컨피그맵을 수정해 직접 Nginx의 옵션을 설정할 수도 있습니다. Nginx 인그레스 컨트롤러의 깃허브 참고




Nginx 인그레스 컨트롤러에 SSL/TLS 보안 연결 적용

인그레스의 장점 중 하나는 앞쪽에 있는 인그레스 컨트롤러에서 편리하게 SSL/TLS 보안 연결을 설정할 수 있다는 것입니다. 따라서 인그레스 컨트롤러가 보안 연결을 수립하기 위한 일종의 관문 역할을 한다고도 볼 수 있습니다.

AWS와 같은 클라우드 환경에서 LoadBalancer 타입의 서비스를 사용할 계획이라면 클라우드 플래폼 자체에서 관리해주는 인증서를 인그레스 컨트롤러에 적용할 수도 있습니다. 예를 들어 AWS의 ACM(AWS Certificate Manager)을 LoadBalancer 타입의 서비스에 제공하는 방법을 생각해 볼 수 있습니다. 이번 실습에서는 직접 서명한 루트 인증서를 통해 Nginx 인그레스 컨트롤러에 적용하는 기초적인 방법을 진행해보겠습니다.

먼저 보안 연결에 사용할 인증서와 비밀키를 생성하겠습니다.

$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
> -keyout tls.key -out tls.crt -subj "/CN=ihp001.example.com/0=ihp001"
Generating a RSA private key
..................................................................................................................................+++++
.....+++++
writing new private key to 'tls.key'
-----
req: Skipping unknown attribute "0"

tls.key, tls.crt라는 비밀키와 인증서가 생성됐습니다. 이 파일들을 통해 tls 타입의 시크릿을 생성합니다.

$ kubectl create secret tls tls-secret --key tls.key --cert tls.crt
secret/tls-secret created

기존에 ingress 야믈 파일에서 TLS 옵션을 추가해 적용해봅시다.

# vi ingress-tls.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-example
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: "nginx"
spec:
  tls:
  - hosts:
    - ihp001.example.com
    secretName: tls-secret
  rules:
  - host: ihp001.example.com
    http:
      paths:
      - path: /echo-hostname
        pathType: Prefix
        backend:
          service:
            name: hostname-service
            port:
              number: 80

기존 ingress-example 인그레스가 있다면 삭제하고, 새롭게 인그레스를 생성합니다. 인그레스를 생성한 뒤에 Nginx 인그레스 컨트롤러로 요청을 보내봅시다.

$ kubectl apply -f ingress-tls.yaml

# 위에 있는 hosts 설정, minikube tunnel 해줘야됨

$ curl http://ihp001.example.com/echo-hostname -k
# -k 옴션은 신뢰할 수 없는 인증서로 보안 연결을 하기 위함

$ curl https://ihp001.example.com/echo-hostname -k

https로 접근했을 때 정상적으로 반환하는 것을 확인할 수 있습니다. 인증서를 통해 보안 연결을 설정했을 때는 http로 접근해도 자동으로 https로 리다이렉트됩니다. 이는 특정 인그레스에 SSL/TLS가 적용됐을 때, Nginx 인그레스 컨트롤러가 https로 리다이렉트하는 annotation 기능인 ssl-redirect를 자동으로 true로 설정하기 때문입니다.




여러 개의 인그레스 컨트롤러 사용하기

Nginx 인그레스 컨트롤러는 기본적으로 nginx라는 이름의 클래스를 가지고 있으며, 이 설정을 변경함으로써 여러 개의 Nginx 인그레스 컨트롤러를 사용할 수도 있고, 인그레스 규칙을 선택적으로 적용할 수도 있습니다. kubernetes.io/ingress.class 주석은 Nginx, Kong, GKE 등의 여러 개의 인그레스 컨트롤러 중 어느 것을 사용할 것인지를 명시합니다. 지금까지는 인그레스를 생성할 때 이 주석의 값으로서 그냥 nginx로 간단하게 사용했지만, 별도의 값을 적용한 Nginx 인그레스 컨트롤러를 생성해 kubernetes.io/ingress.class를 해당 값으로 설정할 수도 있습니다.

Window Docker Desktop, Minikube, WSL 환경에서는 minikube addons enable ingress 명령어로 실행됐던 nginx-controller를 수정해서 테스트해볼 수 있습니다.

$ kubectl get deploy ingress-nginx-controller -n ingress-nginx -o yaml
# 내용 복사
$ kubectl delete deploy ingress-nginx-controller -n ingress-nginx
# 기존 ingress nginx 컨트롤러 deploy 삭제
$ vi deploy-ingress-controller.yaml

복사한 내용을 붙여넣고, args쪽에 --ingress-class=alicek106-nginx를 추가해줍니다.

$ kubectl apply -f deploy-ingress-controller.yaml -n ingress-nginx

--ingress-class의 값이 nginx가 아닌 alicek-nginx이기 때문에 이전에 생성했던 인그레스 규칙은 더 이상 Nginx 인그레스 컨트롤러에 적용되지 않습니다.

$ curl https://ihp001.example.com/echo-hostname -k
# 안된다.

따라서 아래와 같이 인그레스의 kubernetes.io/ingress.class 주석을 alicek106-nginx로 수정해서 ingress를 다시 만들어야지 Nginx 인그레스 컨트롤러가 해당 인그레스의 규칙을 정상적으로 로드해 적용합니다.

$ kubectl delete ing ingress-example
# 기존 인그레스 삭제
# vi ingress-tls.yaml 원래 있던거에서 수정. 
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-example
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: "alicek106-nginx" # 여기 수정
spec:
  tls:
  - hosts:
    - ihp001.example.com
    secretName: tls-secret
  rules:
  - host: ihp001.example.com
    http:
      paths:
      - path: /echo-hostname
        pathType: Prefix
        backend:
          service:
            name: hostname-service
            port:
              number: 80
$ kubectl apply -f ingress-tls.yaml

$ curl https://ihp001.example.com/echo-hostname -k
# 이제 된다. 

이 외에도 Nginx에 적용할 주석 접두어를 설정하는 옵션이나(--annotations-prefix, 기본 값은 nginx.ingress.kubernetes.io), Nginx의 설정을 저장하는 컨피그맵을 지정하는 옵션(--configmap, 기본값은 ingress-nginx 네임스페이스의 nginx-configuration) 등을 수정해 사용할 수 있습니다. 실제 운영 환경을 고려하고 있다면 여러 옵션의 커스터 마이징이 필요할 수 있습니다.

# 리소스 다 삭제
$ kubectl delete po,rs,deploy,svc,ing,secrets --all
$ minikube addons disable ingress





출처
시작하세요! 도커/쿠버네티스(용찬호 저, 위키북스)
example

728x90
반응형
댓글
반응형
250x250
글 보관함
최근에 달린 댓글
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Total
Today
Yesterday
링크