TheKoguryo's 기술 블로그

 Version 2024.04.01
1.2.2.2.6.3 NGINX Ingress Controller에서 TLS termination(feats. Let’s Encrypt)

Ingress Controller에서 외부 수신을 SSL로 하기 위한 설정을 확인합니다.

NGINX Ingress Controller 설치
  1. kubectl 사용이 가능한 Cloud Shell 또는 작업환경에 접속합니다.

  2. nginx ingress controller 설치할 파일 deploy.yaml을 다운로드 받습니다.

    wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.9.5/deploy/static/provider/cloud/deploy.yaml
    
  3. ingress-nginx-controller의 Load Balancer Service 유형에 대한 설정을 annotation으로 추가합니다.

    • oci.oraclecloud.com/load-balancer-type: "lb" 을 추가하여 OCI Load Balancer를 사용하고 관련 설정을 추가합니다.

    • service.beta.kubernetes.io/oci-load-balancer-backend-protocol: "TCP" Ingress 레벨에서 tls를 설정할 것이 때문에 여기는 HTTP가 아닌 TCP 로 설정합니다.

      ...
      ---
      apiVersion: v1
      kind: Service
      metadata:
        labels:
          ...
        name: ingress-nginx-controller
        namespace: ingress-nginx
        annotations:
          oci.oraclecloud.com/load-balancer-type: "lb"
          service.beta.kubernetes.io/oci-load-balancer-shape: "flexible"
          service.beta.kubernetes.io/oci-load-balancer-shape-flex-min: "10"
          service.beta.kubernetes.io/oci-load-balancer-shape-flex-max: "10"
          service.beta.kubernetes.io/oci-load-balancer-backend-protocol: "TCP"
      spec:
        ...
        type: LoadBalancer
      ...
      
  4. 다음 명령으로 NGINX Ingress Controller를 설치합니다.

    kubectl apply -f deploy.yaml
    

생성된 OCI Load Balancer 확인

  1. OCI 콘솔에 로그인합니다.

  2. 좌측 상단 햄버거 메뉴에서 Networking > Load Balancers > Load Balancer로 이동합니다.

  3. 동일한 Public IP로 생성된 Load Balancer를 클릭하여, Listener를 확인합니다. 보면 그림과 같이 HTTP 프로토콜로 80, 443 포트로 수신하고 있습니다.

    image-20240118233735123

DNS 서비스 설정

이미 구입한 Domain Name이 있다는 전제하에 설정하는 과정입니다. 테스트를 위해 따로 구입한 Domain Name(thekoguryo.xyz)을 사용하였습니다. 앞선 실습에서 등록하지 않은 경우 아래와 같이 등록합니다.

  1. 도메인 구입처 또는 OCI DNS 서비스에서 위임하여 서비스하는 경우 OCI DNS 서비스에서 사용한 host를 등록합니다.

  2. 추가할 레코드를 입력하고 제출합니다.

    • Record Type: A - IPv4 Address

    • Name: *.ingress

      • 와일드 카드 형식으로 ingress controller가 사용할 서브 Domain Name을 입력합니다.
    • Address: 매핑할 IP, 여기서는 앞서 만든 OCI Native Ingress Controller의 Load Balancer의 IP 입력

    • 예, GoDaddy DNS 관리화면

    image-20230612185930023

DNS 테스트

  1. nslookup 툴로 등록한 DNS를 테스트 해봅니다. 잘 등록된 것을 알 수 있습니다.

    $ nslookup *.ingress.thekoguryo.xyz
    Server:         127.0.0.11
    Address:        127.0.0.11#53
    
    Non-authoritative answer:
    Name:   *.ingress.thekoguryo.xyz
    Address: 158.180.xx.xxx
    
Self-Signed 인증서 사용하기

테스트 목적으로 Self-Signed 인증서를 만들어 사용하는 방법을 확인해 봅니다. 실제 환경에서는 공인 인증기관에서 발급받은 인증서를 사용합니다. Self-Signed 인증서 발급 절차만 대체되어 TLS Secret 등록과정부터는 동일하게 수행됩니다.

참고 문서

인증서 만들기

  1. Cloud Shell 또는 작업환경에서 다음 명령으로 인증서를 생성합니다. 공인 인증기관에서 발급받은 인증서 사용시 하지 않아도 됩니다.

    openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=nginxsvc/O=nginxsvc"
    
  2. TLS Secret을 만듭니다.

    kubectl create secret tls tls-secret --key tls.key --cert tls.crt
    
  3. 실행결과

    $ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=nginxsvc/O=nginxsvc"
    Generating a 2048 bit RSA private key
    *************************************************************************************************************************************************************+++++
    ****************************************************************************************************************************+++++
    writing new private key to 'tls.key'
    -----
    $ kubectl create secret tls tls-secret --key tls.key --cert tls.crt
    secret/tls-secret created
    

TLS Ingress 자원 배포

  1. 테스트를 위한 샘플 앱을 배포합니다. PATH 기반 라우팅 때 사용한 앱을 그대로 사용합니다.

    kubectl create deployment nginx-blue --image=ghcr.io/thekoguryo/nginx-hello:blue
    kubectl expose deployment nginx-blue --name nginx-blue-svc --port 80
    kubectl create deployment nginx-green --image=ghcr.io/thekoguryo/nginx-hello:green
    kubectl expose deployment nginx-green --name nginx-green-svc --port 80
    
  2. ingress 설정 YAML(tls-termination.yaml)을 작성합니다.

    • spec.tls.secretName으로 앞서 생성한 Self-Signed 인증서 이름을 사용합니다.
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: ingress-tls-termination
    spec:
      ingressClassName: nginx
      tls:
      - secretName: tls-secret
      rules:
      - host: blue.ingress.thekoguryo.xyz
        http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nginx-blue-svc
                port:
                  number: 80
      - host: green.ingress.thekoguryo.xyz
        http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nginx-green-svc
                port:
                  number: 80
    
  3. 작성한 tls-termination.yaml을 배포합니다.

    $ kubectl apply -f tls-termination.yaml 
    ingress.networking.k8s.io/ingress-tls-termination created
    $ kubectl get ingress
    NAME                      CLASS   HOSTS                                                      ADDRESS                     PORTS     AGE
    ingress-tls-termination   nginx   blue.ingress.thekoguryo.xyz,green.ingress.thekoguryo.xyz   10.0.20.170,152.69.xxx.xx   80, 443   43s
    

TLS 적용 결과 검증

  1. ingress rule에서 적용한 host 명으로 각각 접속하여 결과를 확인합니다.

    아래와 같이 https로 접속되고 Self-Signed 인증서로 인한 경고 메시지가 뜹니다.

    image-20230701234445479

  2. 고급을 클릭하고 해당 페이지로 이동을 선택합니다.

    image-20230701011636193

  3. 브라우저 주소창 메뉴를 통해 인증서 정보를 확인합니다. Self-Signed 인증서로 루트 인증서가 신뢰할 수 없다는 경고를 확인할 수 있습니다.

    image-20230701011715020

    image-20230701011734662

Let’s Encrypt & Cert Manager 사용하기

Let’s Encrypt는 무료 인증서 발급 사이트로 TLS에 사용할 인증서를 발급 받을 수 있습니다. 대신 90일 동안만 유효하며 만료전에 갱신해야 합니다. Kubernetes에서는 Cert Manager를 통해 자동으로 갱신할 수 있습니다.

설치 참고 문서

Cert Manager 배포

  1. Cloud Shell 또는 작업환경에서 Cert Manager를 배포합니다.

    kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.3/cert-manager.yaml
    
  2. 설치 확인

    cert-manager namespace에 자원들이 정상 실행중인 확인합니다.

    $ kubectl get all -n cert-manager
    NAME                                           READY   STATUS    RESTARTS   AGE
    pod/cert-manager-77c645c9cd-w5vk4              1/1     Running   0          30s
    pod/cert-manager-cainjector-6678d4cbcd-z58zf   1/1     Running   0          31s
    pod/cert-manager-webhook-996c79df8-bd64t       1/1     Running   0          30s
    
    NAME                           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
    service/cert-manager           ClusterIP   10.96.44.207    <none>        9402/TCP   33s
    service/cert-manager-webhook   ClusterIP   10.96.138.173   <none>        443/TCP    33s
    
    NAME                                      READY   UP-TO-DATE   AVAILABLE   AGE
    deployment.apps/cert-manager              1/1     1            1           31s
    deployment.apps/cert-manager-cainjector   1/1     1            1           32s
    deployment.apps/cert-manager-webhook      1/1     1            1           31s
    
    NAME                                                 DESIRED   CURRENT   READY   AGE
    replicaset.apps/cert-manager-77c645c9cd              1         1         1       31s
    replicaset.apps/cert-manager-cainjector-6678d4cbcd   1         1         1       32s
    replicaset.apps/cert-manager-webhook-996c79df8       1         1         1       31s
    

Let’s Encrypt Issuer 구성

본 예제에서는 Let’s Encrypt에서 제공하는 Staging Issuer, Production Issuer을 사용할 수 있습니다. 여기서는 테스트용도로 Staging Issuer를 사용하겠습니다.

  1. Let’s Encrypt Staging Issuer 설정

    https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/staging-issuer.yaml 파일에서 email 부분만 본인 것으로 수정하여 반영합니다.

    wget https://raw.githubusercontent.com/cert-manager/website/master/content/docs/tutorials/acme/example/staging-issuer.yaml
    
    • kind: 네임스페이스에만 사용되는 Issuer가 아닌 전체 쿠버네티스 클러스터에 사용하기 위해 ClusterIssuer 유형으로 변경합니다.
    • spec.acme.email: 본인 이메일로 변경
    apiVersion: cert-manager.io/v1
    kind: ClusterIssuer
    metadata:
      name: letsencrypt-staging
    spec:
      acme:
        # The ACME server URL
        server: https://acme-staging-v02.api.letsencrypt.org/directory
        # Email address used for ACME registration
        email: user@example.com
        # Name of a secret used to store the ACME account private key
        privateKeySecretRef:
          name: letsencrypt-staging
        # Enable the HTTP-01 challenge provider
        solvers:
          - http01:
              ingress:
                ingressClassName: nginx
    
  2. 설정 적용

    kubectl apply -f staging-issuer.yaml
    
  3. 결과를 확인합니다.

    $ kubectl get ClusterIssuer
    NAME                  READY   AGE
    letsencrypt-staging   True    23s
    

TLS Ingress 자원 배포

  1. 테스트 앱은 이전 그대로 사용합니다.

  2. ingress 설정 YAML(tls-termination-cert-manager.yaml)을 작성합니다.

    • cert-manager.io/cluster-issuer: 방금 생성한 letsencrypt-staging 설정합니다. issuer가 아닌 cluster-issuer 유형을 사용합니다.
    • spec.tls 하위에 tls 저장할 저장소 이름 및 발급받아 사용할 도메인 이름을 지정합니다
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: ingress-tls-termination-cert-manager
      annotations:
        cert-manager.io/cluster-issuer: "letsencrypt-staging"
    spec:
      ingressClassName: nginx
      tls:
      - hosts:
        - green.ingress.thekoguryo.xyz
        - blue.ingress.thekoguryo.xyz
        secretName: ingress-thekoguryo-xyz-tls    
      rules:
      - host: green.ingress.thekoguryo.xyz
        http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nginx-green-svc
                port:
                  number: 80
      - host: blue.ingress.thekoguryo.xyz
        http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nginx-blue-svc
                port:
                  number: 80
    
  3. 기존 테스트 ingress는 삭제하고, 작성한 tls-termination-cert-manager.yaml을 배포합니다.

    $ kubectl delete -f tls-termination.yaml 
    ingress.networking.k8s.io "ingress-tls-termination" deleted
    $ kubectl apply -f tls-termination-cert-manager.yaml
    ingress.networking.k8s.io/ingress-tls-termination-cert-manager created
    $ kubectl get ingress
    NAME                                   CLASS   HOSTS                                                      ADDRESS          PORTS     AGE
    ingress-tls-termination-cert-manager   nginx   green.ingress.thekoguryo.xyz,blue.ingress.thekoguryo.xyz   152.69.xxx.xxx   80, 443   36s
    
  4. 인증서 발급 확인

    지정한 spec.tls.secretName으로 secret이 만들어지고, certificate 상태(READY)가 True가 되면 정상 발급되었습니다.

    $ kubectl get secret
    NAME                         TYPE                DATA   AGE
    ingress-thekoguryo-xyz-tls   kubernetes.io/tls   2      34s
    
    $ kubectl get certificate
    NAME                         READY   SECRET                       AGE
    ingress-thekoguryo-xyz-tls   True    ingress-thekoguryo-xyz-tls   80s
    

Let’s Encrypt Root CA 변경으로 인해 인증오류 해결

  1. Staging Issuer를 사용할 경우, (STAGING) Doctored Durian Root CA X3 만료로 인해 웹브라우저 접속했을 때 인증 오류가 발생하는 경우가 있습니다. 아래 과정을 통해 클라이언트에서 인증서를 등록해 주면 됩니다.

    Production Issuer는 해당 문제가 발생하지 않습니다. 필요한 경우 cert manager issuer를 production issuer를 사용하겠습니다. 대신 production issuer는 생성을 반복할 경우 limit에 걸릴 수 있습니다.

    image-20211207190731003

  2. 해당 에러가 발생하는 경우 변경된 새 Root CA를 let’s encrypt 사이트에서 다운 받아 브라우저에 등록해 줘야 합니다.

  3. (STAGING) 루트 인증서 파일 다운로드

    https://letsencrypt.org/certs/staging/letsencrypt-stg-root-x1.pem

  4. Windows 크롬 브라우저 기준: (STAGING) 루트 인증서 추가

    • 크롬 브라우저 > 설정 > 개인정보 및 보안 > 인증서 관리 로 이동

    • 인증서 가져오기 클릭

      image-20211207190758097

    • 다운받은 파일 선택

      image-20211207190827049

    • 인증서 설치

      image-20211207190845774

    • 인증서 등록후 확인

      신뢰할 수 있는 루트 인증 기관에 방금 등록한 (STAGING) 인증서가 보임

      image-20211207190923997

    • 인증서가 등록후 다시 앱의 웹페이지를 접속하면 인증오류가 발생하지 않고, 인증 경로가 아래와 같이 보이게 됩니다.

      image-20211207185954775

  5. Mac 기준: (STAGING) 루트 인증서 추가

    • 키체인 접근을 실행합니다.

    • 시스템 키체인 잠금 해제합니다

      image-20230701231519703

    • 새로운 키체인 항목 생성 아이콘을 클릭합니다.

      image-20230701231805965

    • 다운받은 (STAGING) 인증서 파일 선택

      image-20230701231818156

    • 추가된 인증서를 더블클릭합니다.

      image-20230701232037330

    • SSL을 항상 신뢰로 변경합니다. 창을 닫으면, 관리자 암호 입력후 저장할 수 있습니다.

      image-20230701232214463

    • 인증서 설정이 완료되었습니다.

      image-20230701232357310

    • 인증서가 등록후 다시 앱의 웹페이지를 접속하면 인증오류가 발생하지 않고, 인증 경로가 아래와 같이 보이게 됩니다. Root CA가 (STAGING) Doctored Durian Root CA X3에서 등록한 (STAGING) Pretend Pear X1으로 변경되었습니다.

      image-20230701232801876

TLS 적용 결과 검증

  1. ingress rule에서 적용한 host 명으로 각각 접속하여 결과를 확인합니다.

    아래와 같이 https로 접속되고 Self-Signed 인증서와 달리 경고 없이 유효한 인증서로 표시됩니다.

    image-20230701232700094

    image-20230701232801876

  2. DNS 대체 주소에 요청한 host가 모두 등록되어, blue 앱도 인증에러 없이 접속됩니다.

    image-20230701233646540



이 글은 개인으로서, 개인의 시간을 할애하여 작성된 글입니다. 글의 내용에 오류가 있을 수 있으며, 글 속의 의견은 개인적인 의견입니다.

Last updated on 18 Jan 2024