TLS / SSL를 위해 보통 인증서를 구매하거나, Let's Encrypt를 통해 무료로 인증서를 발급하고 갱신한다.
나같은 경우엔 홈 서버에서 Cloudflare + Let's Encrypt를 통해 인증서를 발급 받고 자동으로 만료가 되기 전 갱신을 하는 식으로 처리하고 있었으며, 이를 Nginx, 백엔드 어플리케이션 서버에 적용하고 있었다.
이전에는 쿠버네티스 클러스터를 사용하지 않았으므로 위의 작업만 해도 됐지만, 이번에 minikube를 통해 클러스터를 만들게 되면서 고민거리가 생기게 되었다.
쿠버네티스에서도 TLS / SSL을 사용해야 하는 경우가 있는데, 이전 직장에선 인증서 파일 (key, crt, pem 등) 을 Secret으로 생성하여 마운트 시키는 형식으로 사용했었지만 인증서가 갱신이 될 때마다 Secret도 갱신을 해줘야 하는 번거로움이 있었는데 이번에 홈 서버에 클러스터를 구축하는 과정에서 Cert-Manager를 알게 되어 적용을 해보기로 했다.
cert-manager는 Kubernetes 또는 OpenShift 클러스터의 워크로드에 대한 TLS 인증서를 생성하고 만료되기 전에 인증서를 갱신합니다.
cert-manager는 Let's Encrypt , HashiCorp Vault , Venafi , private PKI를 포함한 다양한 인증 기관 에서 인증서를 얻을 수 있습니다 .
cert-manager의 Certificate resource를 사용하면 개인 키와 인증서가 애플리케이션 Pod에 의해 마운트되거나 Ingress 컨트롤러에 의해 사용되는 Kubernetes Secret에 저장됩니다. - 공식 문서 설명
공식 문서에 따르면 Cert-Manager는 쿠버네티스 클러스터의 워크로드에 대한 TLS 인증서를 생성하고 만료되기 전에 인증서를 갱신해주며, 다양한 인증 기관과의 연동이 가능한 도구이다.
Cert-Manager를 몰랐었다면, 인증서를 발급 / 갱신하는 스크립트를 CronJob으로 스케줄링하고 Secret으로 변환하고... 하는 작업을 직접 했었을텐데 이러한 작업들을 자동으로 해준다.
그림에 나온 아키텍처를 살펴보면 Issuer가 인증 기관과 연동되어 있고 이를 Cert-Manager가 관리하며, 발급 / 갱신된 인증서를 Certificates 및 Kubernetes Secret으로 변환해주는 것을 알 수 있다.
Issuer가 인증 기관으로 인증서 발급에 필요한 CertificateRequest / Order를 생성하고 이를 위한 Challenge를 시도하며, 정상적으로 완료될 경우 이 Issuer를 참조하고 있는 Certificate가 인증서 파일을 Secret으로 생성해주는 매커니즘으로 보인다.
이렇게 인증서 갱신 / 발급 및 Certificates, Secret으로 변환해주는 일련의 과정을 각 리소스로 추상화하고 자동화하였기 때문에 인프라를 관리하는 입장에서는 상당히 편리해진 감이 있는 것 같다.
필자는 Helm을 통해 Cert-Manager를 설치하였으며, 추후 소개할 Nginx Fabric Gateway와의 연동을 위해 featureGates 설정을 추가하였다.
먼저 Helm Repository를 추가한다.
# Helm Repo 추가
helm repo add jetstack https://charts.jetstack.io --force-update
이후 지속적으로 다룰 내용이겠지만, Helm으로 오픈 소스를 배포하였을 때 values.yaml을 커스텀해서 배포하는 것을 선호하기 때문에 커맨드 라인에서 부가 옵션을 넣지 않고 Github과 같은 Helm Chart 저장소의 values.yaml을 참고하여 원하는 부분만 수정하여 배포했다.
앞서 이야기했듯이 Nginx Gateway Fabric과의 연동을 위해 featureGates에 내용을 추가하였고, 배포하는 김에 CRD를 같이 설치하기 위해 installCRD 설정도 추가하였다.
# values.yaml
# https://github.com/cert-manager/cert-manager/blob/master/deploy/charts/cert-manager/values.yaml
installCRDs: true
podDnsPolicy: "None"
podDnsConfig:
nameservers:
- "1.1.1.1"
- "8.8.8.8"
featureGates: "ExperimentalGatewayAPISupport=true"
위에서 설정한 values.yaml을 기반으로 Cert-Manager를 배포한다.
helm update --install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace -f values.yaml
이후 kubectl get all -n cert-manager
를 통해 cert-manager 네임스페이스 내에 워크로드들이 제대로 배포되었는지 확인한다.
Cert-Manager가 설치되었다면, 본격적으로 인증 기관에 인증서를 발급하는 역할을 하는 ClusterIssuer와, 이 ClusterIssuer를 참고하여 생성되는 Certificate 리소스를 정의해야 한다. 필자는 위에서 소개하였던대로 Cloudflare, Let's Encrypt를 사용하였다.
아래 명세에 대한 설명을 간단히 하자면 acme 이하의 내용이 인증 기관과 인증서에 사용될 이메일, 그리고 acme 방식에 대한 정의 부분이다.
server는 Let's Encrypt의 prod를 기준으로 하였으며 DNS01 방식으로 처리하였다. Cloudflare의 경우 API 키를 이용한 인증, API 토큰을 이용한 인증 2가지 방식 (apiKeySecretRef, apiTokenSecretRef)이 있는데, 공식 문서에 따르면 다음과 같은 차이가 있다.
API 키 또는 API 토큰의 경우 Opaque 타입으로 Secret을 생성해줘야 한다.
이 때 주의할 점은, 이 Secret의 네임스페이스는 무조건 cert-manager로 지정해줘야 한다. (그렇지 않을 경우 인식을 못한다)
# cluster-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-info
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: blabla@gmail.com
privateKeySecretRef:
name: letsencrypt-info
solvers:
- dns01:
cloudflare:
email: blabla@gmail.com
apiKeySecretRef:
name: cloudflare-api-key-secret
key: api-key
인증기관 별 ClusterIssuer 작성에 대한 내용은 공식 문서에 잘 나와 있으니 참고하면 된다.
https://cert-manager.io/docs/configuration/acme/
ClusterIssuer에 대한 명세가 작성되었다면 바로 배포를 한다.
kubectl apply -f cluster-issuer.yaml
잘 배포가 되었는지 확인하려면 kubectl get clusterissuer
로 확인이 가능하며, READY 상태가 True일 경우 활성화가 완료되었다는 뜻이다.
ClusterIssuer를 배포하였다면 이 ClusterIssuer를 참조하는 Certificate를 배포해야 한다.
여기서 secertName은 인증서 Secret이 생성될 때의 이름을 나타내며 commonName 및 dnsNames는 인증서가 적용될 도메인 주소를 지정한다.
# certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-cocoball-info
spec:
secretName: wildcard-cocoball-info
duration: 2160h0m0s # 90d
renewBefore: 360h0m0s # 15d
issuerRef:
name: letsencrypt-info
kind: ClusterIssuer
commonName: "*.cocoball.info"
dnsNames:
- "*.cocoball.info"
마찬가지로 배포를 진행한다.
kubectl apply -f certificate.yaml
잘 배포가 되었는지 확인하려면 kubectl get certificate
로 확인이 가능하며, 동시에 인증서를 확인하려면 인증서가 배포된 네임스페이스에서 kubectl get secret
을 확인하면 된다.
이 때, 인증서가 문제 없이 발급되었다면 TYPE이 kubernetes.io/tls
로 되어 있어야 하는데, 문제가 발생하였다면 Opaque로 생성이 되었을 것이다.
참고로 세팅 초반에 익숙하지 않아 ClusterIssuer의 상태가 False로 뜨거나 Order가 계속 pending 상태로 뜨는 등 문제가 많이 발생할 수 있는데, 트러블 슈팅 관련해서는 아래 공식 문서의 페이지가 도움이 많이 되니 참고하면 된다.
https://cert-manager.io/docs/troubleshooting/acme/
사실 첫번째 게시글에서도 이야기했었지만 minikube 구축 초기에는 Cert-Manager를 설치하지 않고 Kubernetes Dashboard를 바로 설치하였었는데 Cert-Manager를 설치하게 된 건 Nginx Gateway Fabric을 위함이었다. 그래서 다음 게시글에서는 Gateway API와 Nginx Gateway Fabric에 대해 소개하고자 한다.