Docker

kubenetes 심화 : 인그레스(Ingress)

쿠와와 2021. 2. 5. 17:14

개념 

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

 

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

등등 여러 가지의 기능이 있다. 인그레스의 자체의 기능은 비교적 정해져 있더라도 인그레스의 요청을 처리할 서버로 무엇을 선택하느냐에 따라 기능이 조금씩 달라지기 때문이다. 

 

사용 이유 
우리 애플리케이션이 3개의 디플로이먼트로 생성되어 있다고 가정해보자. 지금까지 배웠던 방법을 이용해 각 디플로이먼트를 외부에 노출해야 한다면 NodePort 또는 LoadBalancer 타입의 서비스 3개를 생성하는 방법이 있다. 즉 각 디폴로이먼트에 대응하는 서비스를 하나씩 연결해 준 셈이다. 
이 방식은 서비스마다 세부적인 설정을 할 때 추가적인 복잡성이 발생하게 된다. 각 서비스와 디플로이먼트에 대해 일일이 설정을 해야하기 때문이다. 
하지만 인그레스를 생성하면 3개의 디플로이먼트를 외부로 노출할 때 단 하나의 URL에 접근하고 인그레스에서 정의한 규칙에 따라 처리된 뒤 적절한 디플로이먼트릐 포드로 전달된다. 

 

이 과정에서 중요한 점은 라우팅 정의나 보안 연결 등과 같은 세부 설정은 서비스와 디플로이먼트가 아닌 인그레스에 의해 수행된다는 것이다. 각 디플로이먼트에 대해 일일이 설정을 적용할 필요 없이 하나의 설정 지점에서 처리 규칙을 정의하기만 하면 된다.

 

즉 외부 요청에 대한 처리 규칙을 쿼버네티스 자체의 기능으로 편리하게 관리 할 수 있다는 것이 핵심이다. 

 

구조 

인그레스는 쿠버네티스에서 ingress라는 이름으로 사용할 수 있다. (ing로 사용가능)

 

먼저 간단한 인그레스를 생성해보자 

ingress-example.yaml

apiVersion: networking.k8s.io/v1beta1
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]
        backend:
          serviceName: hostname-service          # [3]
          servicePort: 80

host : 해당 도메인 이름으로 접근하는 요청에 대해서 처리 규칙을 적용
path : 해당 경로에 들어온 요청을 어느 서비스로 전달할 것인지 정의 

serviceName, servicePort : path로 들어온 요청이 전달될 서비스와 포트 

 

kubectl apply -f ingress-example.yaml

생성은 했지만 아직 아무일도 없다. 단지 요청을 처리하는 규칙을 정의하는 선언적인 오브젝트일 뿐이기 때문이다. 인그레스는 인그레스 컨트롤러라고 하는 툭수한 서버에 적용해야만 그 규칙을 사용할 수 있다.

즉 반드시 인그레스 컨트롤러라는 서버와 함께 사용해야한다. 대표적으로는 Nginx 웹 서버 컨트롤러, Kong API게이트웨어, GKE가 있다. 

 

우리는 Nginx 인그레스 컨트롤러를 사용하겠다. why? 공식적으로 개발되고 있기 때문에 설치 YAMl파일을 공식 깃허브 저장소에서 사용할 수 있기 때문에 

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.35.0/deploy/static/provider/aws/deploy.yaml

웹 서버 생성돼 있는지 확인
kubectl get pods,deployment -n ingress-nginx

서비스 확인
kubectl get svc -n ingress-nginx

 

실제 운영 환경이라면 LoadBalancer 타입에 운 이름을 할당함으로써 Nginx 인그레스 컨트롤러에 접근하는 것이 일반적이다. 하지만 가상 머신처럼 클라우드 환경이 아닌 곳에서 테스트하고 싶다면 NodePort 타입의 서비스를 생성해 사용해도 된다. 

 

ingress-nginx-svc-nodeport.yaml

apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: ingress-nginx
  name: ingress-nginx-controller-nodeport
  namespace: ingress-nginx
spec:
  ports:
  - name: http
    nodePort: 31000
    port: 80
    protocol: TCP
    targetPort: http
  - name: https
    nodePort: 32000
    port: 443
    protocol: TCP
    targetPort: https
  selector:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  type: NodePort

 

echo-hostname을 이용해 인그레스의 동작여부를 확인해보자. 들어오는 요청은 이 디플로이먼트의 포드들로 분산이 될 것이다. 

deployment 

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

service

apiVersion: v1
kind: Service
metadata:
  name: hostname-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

Ngonx 인그레스 컨트롤러의 echo-hostname으로 요청을 전송해 보자. AWS사용하고 있다면 DNS이름으로 GKE라면 IP이름으로 접근

riptutorial.com/curl/example/31718/providing-custom-ip-address-for-a-name

 

curl - Providing custom IP address for a name | curl Tutorial

curl documentation: Providing custom IP address for a name

riptutorial.com

nginx에 접근하기 위한 로드밸런서 타입의 서비스를 생성했을 때 부하 분산기 IP만 할당 받았다면 위에 있는 싸이트에 설명을 볼 수 있다. 사용 예시이다. 

curl --resolve eaxmple.com:80:<부하 분산기 아이피> http://example.com/

NodePort 타이으로 서비스를 생성했다면 다음과 같은 명령어로 테스트해보자 

curl --resolve eaxmple.com:<포트번호>:<노드 중 하나의 아이피 입력>

이제 이미 생성된 인그레스 리소스의 정보를 직접 로드 밸런서의 DNS로 수정해보자 
kubectl edit ingress ingress-example

 

동작 원리 이해 

인그레스 사용 방법 순서

1. 공식 깃허브에서 제공되는 YAML파일로 Nginx 인그레스 컨트롤러 생성

2. Nginx 인그레스 컨트롤러를 외부로 노출하기 위한 서비스를 생성

3. 요청 처리 규칙을 정의하는 인그레스 오브젝트를 생성

4. Nginx 인그레스 컨트롤러로 들어온 요청은 인그레스 규칙에 따라 적절한 서비스로 전달 

 

위의 과정 중 3번에서 인그레스 생성하면 컨트롤러는 자동으로 인그레스를 로드해 웹서버에 적용, 이를 위해 컨트롤러는 항상 인그레스 리소스의 상태를 지켜보고 있다.

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

요청이 실제로 서비스로 전달되는 것은 아니며, 인그레스 컨트롤러는 서비스에 의해 생성된 엔드포인트로 요청을 집접 전달, 즉 서비스,의 ClusterIP가 아닌 엔드포인트의 실제 종착 지점들로 요청이 전달되는 셈이다. 이러한 동작을 바이패스라고 부른다.

 

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

인그레스는 YAML 파일의 주석 항목을 정의함으로써 다양한 옵션을 사용할 수 있다. 

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ingress-example
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /	# hostname-service 의 /로 전달
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - host: alicek106.example.com                   # [1]
    http:
      paths:
      - path: /echo-hostname                     # [2]
        backend:
          serviceName: hostname-service          # [3]
          servicePort: 80

annotaions를 살펴보자 . 

 

nginx.ingress.kubernetes.io/rewrite-target: 인그레스에 정의된 경로로 들어오는 요청을 설정된 경로로 전달 => Nginx에서만 사용 가능 

kubernetes.io/ingress.class: 해당 인그레스 규칙을 어떤 인그레스 컨트롤러에 적용할 것인지를 의미

 

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

ingress-rewrite-target.yaml

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ingress-example
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2 # path의 (.*) 에서 획득한 경로로 전달합니다.
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - host: <여러분이 Nginx 컨트롤러에 접근하기 위한 도메인 이름을 입력합니다>
  #- host: a2cbfefcfbcfd48f8b4c15039fbb6d0a-1976179327.ap-northeast-2.elb.amazonaws.com
    http:
      paths:
      - path: /echo-hostname(/|$)(.*)          # (.*) 을 통해 경로를 얻습니다.
        backend:
          serviceName: hostname-service
          servicePort: 80

 

 

위의 방법처럼 정규표현식을 통해 뒤에 오는 경로를 얻은 뒤 이 값을 rewrite-target에서 사용할 수 있다. 

더 자세한 내용 또는 Nginx 인그레서 컨트롤러가 아닌 컨트롤러는 공식 문서를 참고하자. 

 

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

인그레스 컨트롤러에서 편리하게 SSl/TLS 보안 연결을 설정할 수 있다. 즉 인그레스 컨트롤러 지점에서 인증서를 적용해 두면 요청이 전달되는 애플리케이션에 대해 모두 인증서 처리를 할 수 있다. 따라서 인그레스 컨트롤러가 보안 연결을 수립하기 위한 일종의 관문 역활을 한다고도 볼 수 있다. 

 

먼저 보안 연결에 사용할 인증서와 비밀키를 생성해보자 

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=alicek106.example.com/0=alicek106"

kubectl create secret tls tls-secret --key tls.key --cert tls.crt

ingress-tls.yaml

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ingress-example
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: "nginx"
spec:
  tls:
  - hosts:
    - alicek106.example.com            # 여러분의 도메인 이름을 입력해야 합니다.
    secretName: tls-secret
  rules:
  - host: alicek106.example.com          # 여러분의 도메인 이름을 입력해야 합니다.
    http:
      paths:
      - path: /echo-hostname
        backend:
          serviceName: hostname-service
          servicePort: 80

spec.tls.hosts : 보안 연결을 적용할 도메인 이름
spec.tls.secretName : 생성한 tls 타입의 시크릿 이름

=> 도메인 이름으로 접근하는 요청에 대해 tls-secret 시크릿의 인증 서로 보안 연결을 수립하겠다는 뜻 

 

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

이 부분은 중요하지 않으니 혹시모르면 P399 참고하자