SSL 적용을 위해 HAProxy나 로드밸런서에 인증서를 적용하는 것처럼 k8s에서도 IngressController에 인증서를 적용할 수 있다.
Let’s Encrypt와 DNS 서버를 이용해 k8s에 SSL 인증을 적용해보자.
1. k8s 구성요소 설치
먼저 SSL 적용을 위해 k8s에서 필요한 구성요소를 설치한다. [cert-manager]
$ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.0/cert-manager.yaml
2. bind DNS 서버 연동
서버에 설치된 bind 서버와 연동하기 위해 bind-acme-setup을 설치한다. [github]
안타깝게도 bind는 OS에 따라 셋팅이 워낙 다르기에 쉘 스크립트가 정상적으로 동작하지 않을 가능성이 크다.
필자는 쉘 스크립트에 있는 bind 경로를 실제 bind 경로로 수정하고, 존재하지 않는 경로가 있는 경우 새로 폴더를 만들어 해결하였다.
기존 bind 파일을 링크하고 named 권한을 부여하였는데 그 과정이 순탄치도 않고 정형화하기도 어려워 이 정도로만 글을 남긴다.
$ git clone https://github.com/wreiner/bind-acme-setup.git $ cd bind-acme-setup $ sudo cp bind-acme-setup.sh /usr/sbin/bind-acme-setup.sh $ sudo chmod 755 /usr/sbin/bind-acme-setup.sh $ sudo /usr/sbin/bind-acme-setup.sh aaa.bbb.com ns.aaa.bbb.com $ sudo cat /etc/bind/letsencrypt_keys/aaa.bbb.com.certbot.key key "aaa.bbb.com-certbot-key." { algorithm hmac-sha512; secret "..."; };
3. k8s 연동
BIND에서 생성한 TSIG 키를 사용해 시크릿 매니페스트를 만들고 배포한다.
apiVersion: v1 kind: Secret metadata: name: bind-tsig-key namespace: cert-manager type: Opaque stringData: key: "<TSIG secret key>"
그 후, DNS-01 챌린지를 위한 ClusterIssuer 매니페스트를 만들고 배포한다.
apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-prod spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: example@email.com privateKeySecretRef: name: letsencrypt-prod-account-key solvers: - dns01: rfc2136: nameserver: 100.100.100.1 # BIND 서버 IP tsigSecretSecretRef: name: bind-tsig-key key: key tsigKeyName: "aaa.bbb.com-certbot-key." tsigAlgorithm: HMACSHA512
마지막으로 와일드카드 인증서를 요청하기 위한 매니페스트를 작성하고 배포한다.
참고로 중첩 도메인(xxx.aaa.bbb.com 등)을 사용하는 경우 와일드카드 도메인으로는 인식하지 못하니 중첩 도메인은 dnsNames에 xxx.aaa.bbb.com와 같이 직접 기재해야 한다.
apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: wildcard-aaa-bbb-com namespace: your-namespace spec: secretName: wildcard-tls issuerRef: name: letsencrypt-prod kind: ClusterIssuer commonName: "*.aaa.bbb.com" dnsNames: - "*.aaa.bbb.com" - "aaa.bbb.com"
끝이다.
정상적으로 동작하는지 확인하려면 다음 커맨드를 실행하면 된다.
$ kubectl describe clusterissuer letsencrypt-prod Name: letsencrypt-prod Namespace: Labels: <none> Annotations: <none> API Version: cert-manager.io/v1 Kind: ClusterIssuer Metadata: Creation Timestamp: 2025-04-17T09:16:06Z Generation: 1 Resource Version: 977166 UID: 54be0820-1163-450d-b0cb-faab185517b2 Spec: Acme: Email: example@email.com Private Key Secret Ref: Name: letsencrypt-prod-account-key Server: https://acme-v02.api.letsencrypt.org/directory Solvers: dns01: rfc2136: Nameserver: 100.100.100.1 Tsig Algorithm: HMACSHA512 Tsig Key Name: aaa.bbb.com-certbot-key. Tsig Secret Secret Ref: Key: key Name: bind-tsig-key Status: Acme: Last Private Key Hash: ... Last Registered Email: aaa.bbb.com Uri: https://acme-v02.api.letsencrypt.org/acme/acct/2346376287 Conditions: Last Transition Time: 2025-04-17T09:16:07Z Message: The ACME account was registered with the ACME server Observed Generation: 1 Reason: ACMEAccountRegistered Status: True Type: Ready Events: <none>
4. 인증서 복사
이제 기존의 Ingress 서비스에 SSL을 적용하기만 하면 된다.
필자는 jupyter namespace에서 SSL 서비스를 하려고 한다.
이를 위해서는 ingress-nginx에 있는 기존에 생성한 인증서를 jupyter namespace에 복사해야 한다.
인증서를 수동으로 복사할 수도 있지만 그러면 인증서 갱신마다 인증서를 다시 수동으로 복사해야 하는 번거로움이 있다.
하지만 kubernetes reflector를 사용하면 지정된 네임스페이스에 인증서를 자동으로 복사할 수 있다.
먼저 reflector를 설치하자. [Github]
$ kubectl -n kube-system apply -f https://github.com/emberstack/kubernetes-reflector/releases/latest/download/reflector.yaml
그리고 wildcard 인증서 Issuer에 어노테이션을 추가하면 된다.
이전 단계에서 만든 매니페스트 파일을 수정하여 재배포하면 된다.
아래의 reflector 어노테이션은 인증서가 복사될 수 있도록 하고, 복사 위치를 jupyter namespace로 설정함을 의미한다.
allowed 어노테이션은 말 그대로 복사 허가를 의미하며 auto 어노테이션은 자동 복사 기능을 의미한다.
실수로 auto 어노테이션을 안썼다가 인증서 복사가 안돼 당황한 적이 있다.
apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: wildcard-aaa.bbb.com namespace: ingress-nginx spec: secretName: wildcard-tls issuerRef: name: letsencrypt-prod kind: ClusterIssuer commonName: "*.aaa.bbb.com" dnsNames: - "*.aaa.bbb.com" - "aaa.bbb.com" secretTemplate: annotations: reflector.v1.k8s.emberstack.com/reflection-allowed: "true" reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "jupyter" reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true" reflector.v1.k8s.emberstack.com/reflection-auto-namespaces: "jupyter"
참고로 너무 테스트를 자주 하면 Let’s Encrypt에서 갱신 요청을 거부하니 조심하자.
물론 하루 정도 지나면 풀린다.
5. SSL 서비스 적용
이제 인증서를 Ingress 서비스에 등록하면 된다.
크게 두 가지를 해주어야 한다.
먼저 TLS secret 접근 권한을 부여한다.
# Role apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: <서비스 네임스페이스> name: secret-reader rules: - apiGroups: [""] resources: ["secrets"] verbs: ["get", "watch", "list"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: read-secrets namespace: <서비스 네임스페이스> subjects: - kind: ServiceAccount name: ingress-nginx # Ingress 컨트롤러 서비스 계정 namespace: ingress-nginx roleRef: kind: Role name: secret-reader apiGroup: rbac.authorization.k8s.io
그리고 Ingress 서비스에 TLS와 https 리디렉션 정보를 넣어주면 된다.
metadata.annotations 부분과 spec.tls를 추가하면 된다.
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: my-ingress namespace: <서비스 네임스페이스> annotations: nginx.ingress.kubernetes.io/ssl-redirect: "true" spec: ingressClassName: nginx tls: - hosts: - "service.aaa.bbb.com" secretName: wildcard-tls rules: <생략>
이제 HTTPS를 즐기면 된다.
