NGINX Gateway Fabric Let’s Encrypt, cert-manager 통합
이 문서는 NGINX Gateway Fabric 을 Let’s Encrypt, cert-manager와 통합하여 인증서를 발급하고 관리하는 방법에 관해 설명합니다.
목차
1. NGINX Gateway Fabric, Let’s Encrypt, cert-manager 통합 개요
1-1. 사전 요구사항
2. Let’s Encrypt, cert-manager를 활용한 트래픽 보호
3. NGINX Gateway Fabric, Let’s Encrypt, cert-manager 구성 방법
4. Let’s Encrypt, cert-manager 구성 테스트
5. 문제 해결
1. NGINX Gateway Fabric, Let’s Encrypt, cert-manager 통합 개요
현대 애플리케이션 아키텍쳐에서 클라이언트와 서버 사이의 통신을 보호하는 것은 필수적입니다.
이 과정 중 가장 중요한 단계 중 하나는 모든 통신에 HTTPS를 적용하는 것입니다. 이를 통해 클라이언트와 서버 사이에 전송되는 데이터를 암호화하고, 도청 및 변조를 방지할 수 있습니다. 이를 위해선 신뢰할 수 있는 인증 기관(CA)로부터 발급받은 SSL/TLS 인증서가 필요합니다.
하지만, 인증서 발행 및 관리는 복잡한 수동 프로세스가 될 수 있습니다. 다행히도 인증서 발행 및 관리를 단순화, 자동화 할 수 있는 다양한 서비스와 도구가 존재합니다.
이 문서의 가이드를 따르면 다음과 같이 구성할 수 있습니다.
- Gateway를 사용하여 애플리케이션에 HTTPS 설정 적용.
- Let’s Encrypt를 신뢰할 수 있는 인증 기관(CA)으로 TLS 인증서 발급.
- cert-manager를 사용하여 인증서 발급 및 관리 자동화.
1-1. 사전 요구사항
- Kubernetes 클러스터의 관리자 권한
- Helm과 kubectl이 로컬에 설치됨
- NGINX Gateway Fabric이 클러스터에 배포됨
- DNS-resolve가 가능한 도메인 이름이 필요합니다. 이 도메인은 NGINX Gateway Fabric의 공개 엔드포인트로 resolve되어야 하며, 이 엔드포인트는 인터넷을 통해 접근할 수 있는 외부 IP 주소 혹은 별칭이어야 합니다. 이 과정은 당신의 DNS 제공자에 따라 다릅니다. 이 DNS 이름은 Let’s Encrypt 서버로부터 resolve 가능해야 하며, 동작하기 전에 레코드가 전파되길 기다려야 할 수 있습니다.
2. Let’s Encrypt, cert-manager를 활용한 트래픽 보호

위 사진은 cert-manager ACME challenge와 Gateway API를 사용한 인증서 발급 과정을 단순하게 보여줍니다. 사진에는 과정 중 생성된 모든 Kubernetes 오브젝트가 표기되지 않았음을 명시하세요.
과정은 다음과 같습니다:
- cert-manager를 배포하고 ClusterIssuer를 생성합니다. ClusterIssuer는 Let’s Encrypt를 CA, gateway를 ACME HTTP01 challenge solver로 지정합니다.
- 적용할 도메인(cafe.example.com)을 위한 gateway 리소스를 생성하고 annotaion을 통해 cert-manager 통합 설정을 적용합니다.
- 인증서 발급 과정이 시작됩니다. cert-manager는 Let’s Encrypt와 통신하여 인증서를 얻고, Let’s Encrypt는 ACME challenge를 시작합니다. Challenge의 일환으로, cert-manager는 임시 HTTPRoute 리소스를 생성하여 트래픽을 NGINX Gateway Fabric을 통해 전달하고, 인증서 요청에서 도메인 이름을 제어하고 있음을 확인합니다.
- 도메인이 인증되면 인증서가 발급됩니다. Cert-manager는 키페어를 gateway 리소스가 참조하는 Kubernetes Secret에 저장합니다. 결과적으로, NGINX는 클라이언트의 HTTPS 트래픽을 이 서명된 키페어를 사용하여 terminate하도록 설정됩니다.
- 애플리케이션과 라우팅 규칙을 정의하는 HTTPRoute 리소스를 배포합니다. 정의된 라우팅 규칙은 NGINX가
https://cafe.example.com/coffee로의 요청을 coffee-app 애플리케이션으로 전달하고, gateway 리소스에 정의된 HTTPS 리스너를 사용하도록 설정합니다. - 클라이언트가
https://cafe.example.com/coffee에 연결하면 요청은 coffee-app 애플리케이션으로 라우팅되고, 통신은 cafe-secret에 저장된 키페어를 통해 보호됩니다. - 인증서는 만료에 가까워지면 자동으로 갱신되고, Secret은 새 인증서를 사용하여 갱신되며, NGINX Gateway Fabric은 Secret이 갱신되면 NGINX가 HTTPS termination에 사용하는 파일 시스템의 키페어를 동적으로 업데이트합니다.
3. NGINX Gateway Fabric, Let’s Encrypt, cert-manager 구성 방법
cert-manager 배포
클러스터에 cert-manager을 배포하기 위해 다음 과정을 따릅니다.
- Helm 리포지토리를 추가합니다.
$ helm repo add jetstack https://charts.jetstack.io
$ helm repo update
- cert-manager를 설치하고, GatewayAPI 기능 게이트를 활성화합니다.
$ helm install \
cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set config.apiVersion="controller.config.cert-manager.io/v1alpha1" \
--set config.kind="ControllerConfiguration" \
--set config.enableGatewayAPI=true \
--set crds.enabled=true
ClusterIssuer 생성
이후 인증서 서명 요청을 승인하여 서명된 인증서를 생성하는 인증서 기관(CA)을 나타내는 Kubernetes 리소스인 ClusterIssuer를 생성해야 합니다.
우리는 Let’s Encrypt를 CA 서버로 사용하고 ACME Issuer type를 사용합니다. 우리가 요청하는 도메인에 대한 소유권을 Let’s Encypt가 확인하기 위해 challenges를 완수해야 합니다. 이 과정은 클라이언트가 소유하지 않은 도메인에 대한 인증서를 요청하는 것을 막기 위해 수행됩니다. 우리는 발행자가 HTTP01 challenge를 사용하고, 다음 과정에서 생성할 gateway 리소스를 solver로 사용하도록 설정할 것입니다. HTTP01 challenge에 대해 더 알고싶으시다면, cert-manager 문서를 참고하세요. 다음 YAML 파일을 사용하여 리소스를 생성합니다. email 필드는 실제 이메일 주소로 변경되어야 함을 명시하세요.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
# You must replace this email address with your own.
# Let's Encrypt will use this to contact you about expiring
# certificates, and issues related to your account.
email: my-name@example.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
# Secret resource that will be used to store the account's private key.
name: issuer-account-key
# Add a single challenge solver, HTTP01 using NGINX Gateway Fabric
solvers:
- http01:
gatewayHTTPRoute:
parentRefs: # This is the name of the Gateway that will be created in the next step
- name: gateway
namespace: default
kind: Gateway
cert-manager annotation과 함께 Gateway 배포
이후 gateway를 배포합니다. 아래 YAML 파일의 spec.listeners[1].hostname 필드를 실제 환경에 맞게 변경하여 배포할 수 있습니다.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway
annotations: # This is the name of the ClusterIssuer created in the previous step
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
gatewayClassName: nginx
listeners:
- name: http
port: 80
protocol: HTTP
- name: https
# Important: The hostname needs to be set to your domain
hostname: "cafe.example.com"
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: cafe-secret
일부 구성을 확인해보겠습니다.
- cert-manager annotation은 metadata에 명시되어있습니다. 이 설정을 통해 cert-manager 통합을 활성화하고, cert-manager에게 인증서를 위해 어떤 ClusterIssuer 설정을 사용해야 하는지 알릴 수 있습니다.
- 80 포트에 HTTP, 443 포트에 HTTPS 2개의 listeners가 설정되어 있습니다.
- 80 포트의 HTTP listener는 HTTP01 ACME challenge가 동작하기 위해 필요합니다. 이는 HTTP01 challenge의 일환으로 cert-manager가 ACME 챌린지를 해결하기 위해 임시 HTTPRoute를 생성하며, 이 HTTPRoute가 80번 포트에서 리스너를 필요로 하기 때문입니다. 더 많은 정보를 위해 HTTP01 Gateway API solver 문서를 참고하세요.
- 443 포트의 HTTPS listener는 다음 과정에서 사용할 HTTPRoute입니다. Cert-manager는 이 listener 블록을 위해 인증서를 생성합니다.
- hostname은 필요한 값으로 설정되어야 합니다. ACME 챌린지가 성공적으로 완료되면, letsencrypt-prod ClusterIssuer에서 ‘cafe.example.com’과 같은 도메인에 대한 새로운 인증서가 발급됩니다.
인증서가 발급되면 cert-manager는 클러스터에 인증서 리소스를 생성하고 gateway 리소스와 동일한 네임스페이스에 서명된 키페어가 포함된 cafe-secret Secret을 생성합니다. kubectl을 사용하여 Secret의 생성 여부를 확인할 수 있습니다. Challenge가 완료되고 Secret이 생성되기까지 시간이 소요된다는 것을 명심하세요.
$ kubectl get secret cafe-secret
NAME TYPE DATA AGE
cafe-secret kubernetes.io/tls 2 20s
애플리케이션과 HTTPRoute 배포
이제 coffe deployment와 service를 생성하고, 라우팅 규칙을 설정할 수 있습니다. 다음 YAML 파일을 사용하여 deployment와 service를 생성할 수 있습니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: coffee
spec:
replicas: 1
selector:
matchLabels:
app: coffee
template:
metadata:
labels:
app: coffee
spec:
containers:
- name: coffee
image: nginxdemos/nginx-hello:plain-text
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: coffee
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: coffee
coffee 애플리케이션을 위한 라우팅 규칙을 설정하기 위해 HTTPRoute를 배포합니다. parentRefs 섹션은 앞선 과정에서 설정된 listener을 나타낸다는 것을 명시하세요.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: coffee
spec:
parentRefs:
- name: gateway
sectionName: https
hostnames: # Update the hostname to match what is configured in the Gateway resource
- "cafe.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /coffee
backendRefs:
- name: coffee
port: 80
4. Let’s Encrypt, cert-manager 구성 테스트
모든 것이 올바르게 구성되었는지 테스트하기 위해 curl 명령어를 사용하여 https://cafe.example.com/coffee와 같은 엔드포인트로 연결할 수 있습니다. curl 명령어를 사용하여 확인하기 위해, -v 옵션을 사용하여 출력을 확장하고 제시된 인증서를 확인할 수 있습니다.
$ curl https://cafe.example.com/coffee -v
다음과 같은 출력이 나타납니다.
* Trying 54.195.47.105:443...
* Connected to cafe.example.com (54.195.47.105) port 443 (#0)
* ALPN: offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/ssl/cert.pem
* CApath: none
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* ALPN: server accepted http/1.1
* Server certificate:
* subject: CN=cafe.example.com
* start date: Aug 11 08:22:11 2023 GMT
* expire date: Nov 9 08:22:10 2023 GMT
* subjectAltName: host "cafe.example.com" matched cert's "cafe.example.com"
* issuer: C=US; O=Let's Encrypt; CN=R3
* SSL certificate verify ok.
* using HTTP/1.1
> GET /coffee HTTP/1.1
> Host: cafe.example.com
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.25.1
< Date: Fri, 11 Aug 2023 10:03:21 GMT
< Content-Type: text/plain
< Content-Length: 163
< Connection: keep-alive
< Expires: Fri, 11 Aug 2023 10:03:20 GMT
< Cache-Control: no-cache
<
Server address: 192.168.78.136:8080
Server name: coffee-9bf875848-xvkqv
Date: 11/Aug/2023:10:03:21 +0000
URI: /coffee
Request ID: e64c54a2ac253375ac085d48980f000a
* Connection #0 to host cafe.example.com left intact
5. 문제 해결
- cert-manager 설치 및 issuer 설정에 관련된 문제는 cert-manager 문제 해결 가이드를 참고하세요.
- HTTP01 ACME challenge 문제를 해결하기 위해, ACME 문제 해결 가이드를 참고하세요.
- Gateway 리소스를 통해 HTTP01 challenge를 수행하려면 HTTPS redirect는 설정되지 않아야 합니다.
- cert-manager가 생성하는 임시 HTTPRoute는 cert-manager와 Let’s Encrypt 서버 사이의 트래픽을 NGINX Gateway Fabric을 통해 라우팅합니다. 만약 challenge가 실패하면 NGINX 로그를 통해 ACME challenge 요청을 확인하는 것이 도움이 될 수 있습니다. 다음과 같은 내용을 확인할 수 있습니다.
kubectl logs <pod-name> -n nginx-gateway -c nginx
<...>
52.208.162.19 - - [15/Aug/2023:13:18:12 +0000] "GET /.well-known/acme-challenge/bXQn27Lenax2AJKmOOS523T-MWOKeFhL0bvrouNkUc4 HTTP/1.1" 200 87 "-" "cert-manager-challenges/v1.12.0 (linux/amd64) cert-manager/bd192c4f76dd883f9ee908035b894ffb49002384"
52.208.162.19 - - [15/Aug/2023:13:18:14 +0000] "GET /.well-known/acme-challenge/bXQn27Lenax2AJKmOOS523T-MWOKeFhL0bvrouNkUc4 HTTP/1.1" 200 87 "-" "cert-manager-challenges/v1.12.0 (linux/amd64) cert-manager/bd192c4f76dd883f9ee908035b894ffb49002384"
52.208.162.19 - - [15/Aug/2023:13:18:16 +0000] "GET /.well-known/acme-challenge/bXQn27Lenax2AJKmOOS523T-MWOKeFhL0bvrouNkUc4 HTTP/1.1" 200 87 "-" "cert-manager-challenges/v1.12.0 (linux/amd64) cert-manager/bd192c4f76dd883f9ee908035b894ffb49002384"
52.208.162.19 - - [15/Aug/2023:13:18:18 +0000] "GET /.well-known/acme-challenge/bXQn27Lenax2AJKmOOS523T-MWOKeFhL0bvrouNkUc4 HTTP/1.1" 200 87 "-" "cert-manager-challenges/v1.12.0 (linux/amd64) cert-manager/bd192c4f76dd883f9ee908035b894ffb49002384"
52.208.162.19 - - [15/Aug/2023:13:18:20 +0000] "GET /.well-known/acme-challenge/bXQn27Lenax2AJKmOOS523T-MWOKeFhL0bvrouNkUc4 HTTP/1.1" 200 87 "-" "cert-manager-challenges/v1.12.0 (linux/amd64) cert-manager/bd192c4f76dd883f9ee908035b894ffb49002384"
3.128.204.81 - - [15/Aug/2023:13:18:22 +0000] "GET /.well-known/acme-challenge/bXQn27Lenax2AJKmOOS523T-MWOKeFhL0bvrouNkUc4 HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
23.178.112.204 - - [15/Aug/2023:13:18:22 +0000] "GET /.well-known/acme-challenge/bXQn27Lenax2AJKmOOS523T-MWOKeFhL0bvrouNkUc4 HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
35.166.192.222 - - [15/Aug/2023:13:18:22 +0000] "GET /.well-known/acme-challenge/bXQn27Lenax2AJKmOOS523T-MWOKeFhL0bvrouNkUc4 HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
<...>
댓글을 달려면 로그인해야 합니다.