Knowledge Center Monthly Newsletter - March 2025
Stay up to date with the latest from the Knowledge Center. See all new and updated Knowledge Center articles published in the last month and re:Post’s top contributors.
Amazon EKS 클러스터를 위한 cert-manager를 통한 인증서 발급 및 TLS 지원
본 기사에서는 AWS EKS 클러스터 내 cert-manager를 설치하여 Ingress에서 cert-manager로부터 인증서를 가져오고 Secret으로 저장하여, 클러스터에 배포한 웹에 연결한 Route53에 등록한 도메인에 HTTPS를 통해 접근할 수 있는 방법을 설명합니다.
Kubernetes 클러스터에는 인증서 관리를 위한 모듈인 cert-manager[1]가 존재합니다. cert-manager는 사용하는 워크로드에 대한 TLS 인증서를 생성하고 만료되기 전 갱신을 수행하게 됩니다. cert-manager의 Certificate 리소스를 사용하게 되면, Private Key와 인증서가 Kubernetes Secret 오브젝트에 저장됩니다.
인증 과정에서 가장 중요한 오브젝트는 Issuer입니다. Issuer[2]란 인증서에 서명을 할 수 있는 인증기관(CA : Certificate Authority)를 나타내는 리소스입니다. Issuer 및 ClusterIssuer로 구분되는데 이는 리소스의 적용 범위에 대한 차이고, 특정 Namespace로 국한하기 위해서는 Issuer로, 클러스터 전체에 대해 적용하기 위해서는 ClusterIssuer로 선택하여 사용할 수 있습니다.
cert-manager를 통한 인증서 및 Secret 발급 과정에 대해 간단히 설명해보도록 하겠습니다. 본 기사에서는 Issuer를 통한 Issued 프로세스를 확인하기 위해 Issuer의 배포는 과정 중간에 진행됩니다.
Prerequisite
-
cert-manager 설치
$ helm repo add jetstack https://charts.jetstack.io $ helm repo update $ helm install cert-manager jetstack/cert-manager —namespace cert-manager —create-namespace —set installCRDs=true
-
ingress-nginx 설치
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/cloud/deploy.yaml
-
Target Service, Deployment/Pod 배포
$ cat application.yaml --- apiVersion: v1 kind: Service metadata: name: nginx-service namespace: sandbox spec: selector: app: nginx-deployment ports: - protocol: TCP port: 80 targetPort: 80 --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: sandbox spec: replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: public.ecr.aws/nginx/nginx:latest ports: - name: tcp containerPort: 80
-
Public 도메인 준비 인증서 발급을 위하여 public 도메인을 준비합니다.
-
Route53 도메인 연결 및 CLB 연결 ingress-nginx를 설치하면 기본적으로 CLB(Classic Load Balancer)가 생성됩니다. 준비한 public 도메인의 CNAME에 CLB의 DNS name을 연결합니다. 본 실습에서는 Route53에 준비한 도메인을 등록하여 Nameserver를 변경한 상태에서 CNAME을 추가하여 CLB를 연결합니다.
Step 1. Ingress 배포
Ingress를 배포할 때 tls 설정 및 인증 기관인 Issuer를 함께 정의합니다.
$ cat ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
namespace: sandbox
annotations:
cert-manager.io/issuer: nginx-issuer
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
rules:
- host: <test-host.com>
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80
tls:
- hosts:
- <test-host.com>
secretName: nginx-app-tls-secret
이 때, Issuer가 없는 상태임에도 cert-manager.io/issuer로 nginx-issuer
를 정의하였고, tls 통신을 위한 정보로 host, secret을 정의하였기에 내부적으로 다음과 같은 작업이 수행됩니다.
Certificate[3] 생성
아직 Issuer(nginx-issuer)가 없는 상태이기 때문에 Issuing 상태 및 Ready:False 상태를 가진 Certificate가 생성됩니다.
$ kubectl get certificate -n sandbox
NAME READY SECRET AGE
nginx-app-tls-secret False nginx-app-tls-secret 3h11m
$ kubectl get certificate nginx-app-tls-secret -n sandbox -o yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
generation: 1
name: name: nginx-app-tls-secret
...
spec:
dnsNames:
- <test-host.com>
issuerRef:
group: cert-manager.io
kind: Issuer
name: nginx-issuer
secretName: nginx-app-tls-secret
usages:
- digital signature
- key encipherment
status:
conditions:
- lastTransitionTime: "2024-03-20T05:23:37Z"
message: Issuing certificate as Secret does not exist
observedGeneration: 1
reason: DoesNotExist
status: "False"
type: Ready
- lastTransitionTime: "2024-03-20T05:23:37Z"
message: Issuing certificate as Secret does not exist
observedGeneration: 1
reason: DoesNotExist
status: "True"
type: Issuing
nextPrivateKeySecretName: nginx-app-tls-secret-2hh6m
Secret[7] 생성
기존에 존재하는 Secret이 없기 때문에 임시로 임시 Private Key를 생성합니다. 그 결과 아래와 같이 Secret이 생성됩니다.
$ kubectl get secret -n sandbox
NAME TYPE DATA AGE
nginx-app-tls-secret-2hh6m Opaque 1 3h12m
$ kubectl get secret nginx-app-tls-secret-2hh6m -n sandbox -o yaml
apiVersion: v1
items:
- apiVersion: v1
data:
tls.key: <Base64 Encoded Private Key>
kind: Secret
metadata:
generateName: acme-nginx-app-tls-secret-
labels:
cert-manager.io/next-private-key: "true"
controller.cert-manager.io/fao: "true"
name: nginx-app-tls-secret-2hh6m
namespace: sandbox ownerReferences:
- apiVersion: cert-manager.io/v1
blockOwnerDeletion: true
controller: true
kind: Certificate
name: nginx-app-tls-secret
uid: 9945bcf5-01bb-4352-8ae4-70817bcaf4e3
resourceVersion: "238823"
uid: adf8544b-991c-4c7e-8320-c317c768cae0
type: Opaque
kind: List
metadata:
resourceVersion: ""
CertificateRequest[4] 생성
또한 Certificate가 인증받기 위한 요청을 처리하기 위해 CertificateRequest 오브젝트를 자동으로 생성합니다. 이 때 Secret의 Private Key를 통한 PEM encoded 정보가 Request에 포함됩니다.
$ kubectl get certificaterequest -n sandbox
NAMESPACE NAME APPROVED DENIED READY ISSUER REQUESTOR AGE
sandbox nginx-app-tls-secret-1 True False nginx-issuer system:serviceaccount:cert-manager:cert-manager 3h13m
$ kubectl get certificaterequest nginx-app-tls-secret-1 -n sandbox -o yaml
apiVersion: cert-manager.io/v1
kind: CertificateRequest
metadata:
annotations:
cert-manager.io/certificate-name: nginx-app-tls-secret
cert-manager.io/certificate-revision: "1"
cert-manager.io/private-key-secret-name: nginx-app-tls-secret-2hh6m
...
spec:
...
issuerRef:
group: cert-manager.io
kind: Issuer
name: acme-nginx-app-issuer
request: <Encoded Request>
status:
conditions:
- lastTransitionTime: "2024-03-20T05:23:37Z"
message: Certificate request has been approved by cert-manager.io
reason: cert-manager.io
status: "True"
type: Approved
- lastTransitionTime: "2024-03-20T05:23:37Z"
message: 'Referenced "Issuer" not found: issuer.cert-manager.io "nginx-issuer"
not found'
reason: Pending
status: "False"
type: Ready
이 역시 아직 Issuer가 존재하지 않기 때문에, Pending 상태로 유지됩니다.
Step 2. Issuer 생성
이제 기존에 생성된 리소스들의 인증 과정을 진행하기 위해 Issuer를 생성합니다.
$ cat issuer.yaml
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: nginx-issuer
namespace: sandbox
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: <email address>
privateKeySecretRef:
name: nginx-app-private-key
solvers:
- http01:
ingress:
ingressClassName: nginx
인증을 위해 CA 서버로 Let’s Encrypt(https://letsencrypt.org/)를 사용합니다. Let’s Encrypt는 staging/production이 구분되어 있으며, 일정 시간 내 여러번 인증서를 요청하는 경우 일정 시간 내에는 인증서 발급이 제한되므로 테스트시에는 staging 경로를 사용할 수 있도록 합니다.
- staging : https://acme-staging-v02.api.letsencrypt.org/directory
- production : https://acme-v02.api.letsencrypt.org/directory
Issuer가 생성되고 나면 다음과 같은 과정이 진행됩니다.
Order[6] 생성
Order는 CertificateRequest를 처리하기 위해 Issuer가 생성하게 되며 이어 Domain마다 각각 Challenge[5]를 생성합니다.
$ kubectl get order -A --watch
nginx-app-tls-secret-1-310547795 0s
nginx-app-tls-secret-1-310547795 pending 0s
nginx-app-tls-secret-1-310547795 ready 22s
nginx-app-tls-secret-1-310547795 valid 22s
Challenge 생성
$ kubectl get challenge -A --watch
nginx-app-tls-secret-1-310547795-2104847025 <test-host.com> 0s
nginx-app-tls-secret-1-310547795-2104847025 pending <test-host.com> 2s
nginx-app-tls-secret-1-310547795-2104847025 valid <test-host.com> 21s
nginx-app-tls-secret-1-310547795-2104847025 valid <test-host.com> 22s
현재 샘플상으로는 Domain이 1개이기 때문에 하나의 Challenge가 생성되었고, 해당 Challenge는 Let’s Encrypt를 통해 해당 도메인의 소유자가 맞는지 확인하고, 확인이 완료되면 Order 내 인증서 정보를 업데이트하고 Order 역시 상태가 변경됩니다. 이 때, 확인을 위해 생성된 Challenge 오브젝트는 완료 후 자동으로 삭제됩니다.
CertificateRequest 변경
이제 Order 및 Challenge를 통해 도메인이 확인되었기 때문에 인증서를 발급받기 위한 CertificateRequest가 어떻게 변경되었는지 확인해보겠습니다.
$ kubectl get certificaterequest -n sandbox
NAMESPACE NAME APPROVED DENIED READY ISSUER REQUESTOR AGE
sandbox nginx-app-tls-secret-1 True True nginx-issuer system:serviceaccount:cert-manager:cert-manager 3h28m
Approve는 되었으나 Ready가 false인 상태에서, true로 변경된 것을 확인하실 수 있습니다. 상세 정보를 확인해보면 다음과 같습니다.
$ kubectl get certificaterequest nginx-app-tls-secret-1 -n sandbox -o yaml
apiVersion: cert-manager.io/v1
kind: CertificateRequest
metadata:
annotations:
cert-manager.io/certificate-name: nginx-app-tls-secret
cert-manager.io/certificate-revision: "1"
cert-manager.io/private-key-secret-name: nginx-app-tls-secret-2hh6m
...
spec:
...
issuerRef:
group: cert-manager.io
kind: Issuer
name: acme-nginx-app-issuer
request: <Encoded Request>
status:
certificate: <Encoded Certificate> # 신규 생성
conditions:
- lastTransitionTime: "2024-03-20T05:23:37Z"
message: Certificate request has been approved by cert-manager.io
reason: cert-manager.io
status: "True"
type: Approved
- lastTransitionTime: "2024-03-20T08:52:30Z" # 상태, 시간 등 변경
message: Certificate fetched from issuer successfully
reason: Issued
status: "True"
type: Ready
Certificate 변경
다른 정보는 모두 동일하나 Status 내 Ready 타입의 값이 Issued로 상태로 변경되었고, 그에 따라 certificate 정보가 추가되었습니다. 이제 인증서가 발급되었습니다.이제 Certificate가 어떻게 변경되었는지 확인해보겠습니다.
$ kubectl get certificate -n sandbox
NAME READY SECRET AGE
nginx-app-tls-secret True nginx-app-tls-secret 3h28m
$ kubectl get certificate nginx-app-tls-secret -n sandbox -o yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
generation: 1
name: name: nginx-app-tls-secret
...
spec:
dnsNames:
- <test-host.com>
issuerRef:
group: cert-manager.io
kind: Issuer
name: nginx-issuer
secretName: nginx-app-tls-secret
usages:
- digital signature
- key encipherment
status: # 인증서 정보 변경
conditions:
- lastTransitionTime: "2024-03-20T08:52:30Z"
message: Certificate is up to date and has not expired
observedGeneration: 1
reason: Ready
status: "True"
type: Ready
notAfter: "2024-06-18T07:52:29Z"
notBefore: "2024-03-20T07:52:30Z"
renewalTime: "2024-05-19T07:52:29Z"
revision: 1 # CertificateRequest revision과 일치
Certificate 역시 false 상태였던 Ready 값이 true로 바뀌고 issued 된 것을 확인하실 수 있습니다. 또한 CertificateRequest의 revision 정보가 포함되어 어떠한 Request를 통해 발급된 Certificate 인지 정보를 확인할 수 있습니다.
Secret 변경
Certificate가 Ready:True로 변경되는 과정에서 Secret 역시 변경되었습니다.
$ kubectl get secret -A --watch
nginx-app-private-key Opaque 1 0s
nginx-app-tls-secret kubernetes.io/tls 2 0s
nginx-app-tls-secret-2hh6m Opaque 1 3h28m
$ kubectl get secret -n sandbox
NAMESPACE NAME TYPE DATA AGE
sandbox nginx-app-private-key Opaque 1 8m8s
sandbox nginx-app-tls-secret kubernetes.io/tls 2 7m45s
기존의 nginx-app-els-secret-2hh6m
은 임시로 생성되었던 Secret이기 때문에 삭제되었고, 실제 인증서 정보가 포함된 Secret인 nginx-app-tls-secret
이 새로 생성된 것을 확인하실 수 있습니다.
$ kubectl get secret nginx-app-tls-secret -n sandbox -o yaml
apiVersion: v1
data:
tls.crt: <Encoded Certificate>
tls.key: <Encoded RSA Private Key>
kind: Secret
metadata:
annotations:
cert-manager.io/alt-names: <test-domain.com>
cert-manager.io/certificate-name: nginx-app-tls-secret
cert-manager.io/common-name: <test-domain.com>
cert-manager.io/ip-sans: ""
cert-manager.io/issuer-group: cert-manager.io
cert-manager.io/issuer-kind: Issuer
cert-manager.io/issuer-name: nginx-issuer
cert-manager.io/uri-sans: ""
creationTimestamp: "2024-03-20T08:52:30Z"
labels:
controller.cert-manager.io/fao: "true"
name: nginx-app-tls-secret
namespace: acme-ns
resourceVersion: "273468"
uid: 48e2ae2d-824a-46c1-a025-24899820b2f6
type: kubernetes.io/tls
이렇게 실제 Secret이 신규 생성되고 나서 Certificate는 Ready:True로 상태가 변경되게 됩니다.
이제 <test-domain.com>에 접속하면 신뢰할 수 있는 사이트
라는 표식과 함께 HTTPS를 통해 접속이 가능합니다.
Issuer의 종류에는 본 기사에서 다룬 ACME(Automatic Certificate Management Environment)는 물론 Cluster 내에서 사용하기 위해 Self-Signed가 있으며 다수의 in-tree issuer가 존재하합니다. ACM Private CA를 이용하는 경우를 위해 aws-privateca-issuer Helm chart[9]를 제공하고 있으며 이에 대해 자세한 단계가 설명된 블로그[10]를 참고하실 수 있습니다.
References:
[1] https://cert-manager.io/docs/
[2] https://cert-manager.io/docs/configuration/
[3] https://cert-manager.io/docs/usage/certificate/
[4] https://cert-manager.io/docs/usage/certificaterequest/
[5] https://cert-manager.io/docs/reference/api-docs/#acme.cert-manager.io/v1.Challenge
[6] https://cert-manager.io/docs/reference/api-docs/#acme.cert-manager.io/v1.Order
[7] https://kubernetes.io/docs/concepts/configuration/secret/
[8] https://cert-manager.io/docs/configuration/issuers/
관련 콘텐츠
- 질문됨 일 년 전lg...
- 질문됨 10달 전lg...
- AWS 공식업데이트됨 10달 전
- AWS 공식업데이트됨 6달 전
- AWS 공식업데이트됨 일 년 전
- AWS 공식업데이트됨 3달 전