들어가며
배포하려는 서비스에 HTTPS연결을 통한 SSL 인증을 추가하려면, 사설 또는 기관 인증서를 사용해야 합니다. certbot-nginx라는 패키지를 이용해서 자동으로 이에 대한 처리를 할 수 있지만, 쿠버네티스 환경에서는 cert, pem 파일을 각 Pod마다 PV 등을 통해 공유해줘야 해서 다소 번거롭습니다.
그리하여, API gateway나 Ingress를 통해 이에대한 처리를 하는 것이 바람직하다 생각하여 여러 방법을 찾던 중, cert-manager라는 클라우드 네이티브 앱을 발견했습니다. 이번 포스팅에서는 이를 적용하는 과정에 대해 다뤄보도록 하겠습니다.
cert-manager: 개요
여러 기능을 제공하는것으로 보입니다만, 여기서 제가 관심을 가지게 된 부분은 "Automated issuance and renewal of certificates to secure ingress with TLS"라는 대목입니다. 즉, ingress를 통해서 자동으로 TLS를 통한 인증정보를 추가/갱신해준다는 것입니다.
( +---------+ )
( | Ingress | ) Optional ACME Only!
( +---------+ )
| |
| +-------------+ +--------------------+ | +-------+ +-----------+
|-> | Certificate |----> | CertificateRequest | ----> | | Order | ----> | Challenge |
+-------------+ +--------------------+ | +-------+ +-----------+
|
Documentation을 잘 살펴보면, 위와 같은 절차로 Ingress를 통한 인증서 발급, 관리가 이루어짐을 확인할 수 있었습니다. Ingress에 특정 도메인에 대한 요청이 들어오면, 해당 도메인에 대한 요청은 항상 HTTPS으로 처리하게 됩니다. 이때, 적합한 SSL/TLS인증서가 없다면, Let's Encrypt를 통해 사설 인증서를 발급하고 검증하는 과정을 거치게 됩니다.
이때, cert-manger는 인증서 발급에 대한 요청(Order)을 생성하여 ACME(Automated Certificate Management Environment) 서버에 발급요청을 보내게 됩니다. 이후 ACME서버는 요청을 보낸 측에 정말 그 도메인을 소유하고 있는지 확인하는 요청을 보내고, 검증된 경우에 SSL인증서를 cert-manager로 반환하고 Order가 완료되게 됩니다. 이에 대한 더 자세한 설명은 다음 링크를 확인해 주세요 (여기)
cert-manager: 설치
[01] 구성요소 준비
아래 명령어를 통해 클러스터에 cert-manger를 배포합니다.
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.yaml
관련된 공식 가이드는 아래와 같습니다.
[02] cert-issuer 명시
apiVersion: cert-manager.io/v1
kind: Issuer
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: <여러분의 이메일>
# 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
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: letsencrypt-prod
spec:
acme:
# The ACME server URL
server: https://acme-v02.api.letsencrypt.org/directory
# Email address used for ACME registration
email: <여러분의 이메일>
# Name of a secret used to store the ACME account private key
privateKeySecretRef:
name: letsencrypt-prod
# Enable the HTTP-01 challenge provider
solvers:
- http01:
ingress:
ingressClassName: nginx
[03] Ingress 요소 추가
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
annotations:
cert-manager.io/issuer: "letsencrypt-prod" # 확인!
kubernetes.io/ingress.class: "nginx" # 확인!
acme.cert-manager.io/http01-edit-in-place: "true" # 확인!
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
tls:
- hosts:
- <HTTPS를 적용하려는 도메인> # 확인!
secretName: <cert, pem 키를 저장할 secret> # 확인!
rules:
- host: <HTTPS를 적용하려는 도메인> # 확인!
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: <Service 이름>
port:
number: <Service 포트>
이미 구성해 둔 Ingress요소가 있다면, "확인!"이라는 주석이 있는 부분을 확인해 주시면 됩니다. 각각에 대한 설명은 아래와 같습니다.
- cert-manager.io/issuer: Issuer를 명세할 때 metadata.name에 명세해 준 이름. 즉, 어떤 issuer를 사용할 것인지
- kubernetes.io/ingress.class: 해당 Ingress를 구분할 class. Issuer의 solvers.http01.ingress.ingressClassName과 일치하면 됩니다. 즉, 어떤 ingress 요소로 HTTP-01 Challenge를 해결할 것 인지 명세합니다.
- acme.cert-manager.io/http01-edit-in-place: 임시 ingress를 추가로 생성하지 않고, 해당 ingress에 rule을 추가하여 Challenge를 처리할 것 임을 명시합니다. 참고로, Challenge는 아래와 같은 형태로 요청됩니다.
http://<YOUR_DOMAIN>/. well-known/acme-challenge/<TOKEN>
- spec.tls.hosts: 어떤 도메인에 TLS인증을 수행할 것인지 명시합니다. 해당 도메인은 spec.rules.host에도 추가되어야 합니다.
- spec.tls.secretName: 별도로 참조할 일이 있진 않겠지만, Challenge가 성공한 경우 명시한 이름으로 secret을 생성하여 cert, pem 파일을 관리합니다.
[03-1] Challenge 트러블슈팅
https인증서가 발급되지 않고, 배포된 서비스에 접속해 보면 "Kubernetes Ingress Controller Fake Certificate"라는 알 수 없는 인증서가 보일 수 있습니다. 이는 제대로 된 인증서 발급이 이루어지지 않은 경우입니다. 이와 관련된 이슈는 아래를 참고해 주세요.
원인을 찾기 위해 우선 Order, Challenge가 어떤 상태인지 확인해 볼 필요가 있습니다.
# Order에 대한 확인
kubectl describe order
# Challenge에 대한 확인
kubectl describe challenge
저의 경우, 특히 Challenge가 올바르게 이루어지지 않은 경우가 많았습니다. 총 두 가지 에러를 마주할 수 있었는데, 정리하면 아래와 같습니다.
- 200을 반환해야 하는데 404가 반환되는 경우: 너무 많은 경우의 수가 있지만, 제 경우에는 "/. well-known/acme-challenge/<TOKEN>"에 대한 요청을 challenge를 처리할 임시 ingress가 받지 않고, 기존 서비스 컨테이너가 받아서 생긴 문제였습니다.
- 다른 Pod가 hostNetwork: true로 설정되어 있지 않은지 확인
- 호스트 Node에 다른 서비스, 프로세스가 80번 포트를 LISTEN 하는지 확인
- 토큰을 반환해야 하는데 <! DOCTYPE>... 어쩌고 하는 내용을 반환하는 경우: 이 경우에도 마찬가지로, 다른 서비스가 challenge요청을 가로채어 생긴 문제였습니다. 역시나 같은 방법으로 해결합시다.
- 다른 Pod가 hostNetwork: true로 설정되어 있지 않은지 확인
- 호스트 Node에 다른 서비스, 프로세스가 80번 포트를 LISTEN 하는지 확인
제가 겪지 않은 더 많은 오류에 대한 트러블슈팅 가이드는 아래 공식문서에서 확인해 볼 수 있습니다.
드디어!
앞선 과정을 문제없이 따라왔다면, Ingress를 통해 공개된 서비스가 올바른 인증서를 통해 동작함을 확인해볼 수 있습니다.
'Cloud > Kubernetes' 카테고리의 다른 글
[Kubernetes] 온프레미스 환경에 싱글노드 쿠버네티스 구축: 01 - 클러스터 준비 (1) | 2023.07.15 |
---|