본문 바로가기

Technical/Cloud, Virtualization, Containers

[Kubernetes] Traefik v2와 cert-manager 를 이용한 secure service 구현

GCP, AWS와 같은 Public cloud나 On-premise 에서도, 외부에 secure web 서비스를 제공하기 위해 http entrypoint에 대해
유효한 SSL 인증키를 적용하기 위해서는 많은 번거로운 작업 과정을 거쳐야 한다. 그 방법도 여러 가지가 존재하겠지만 여기서는
Kubernetes의 장점을 살려, 간단한 설정과 수작업이 최소화 된 간편 버전의 솔루션을 직접 구현해 보고 정리한 문서를, 따라하기 버전으로 소개하려 한다.

Traefik ingress 의 entrypoint에 대해 Let's Encrypt 로부터 유효(정식) 인증키를 적용해 주는 cert-manager(Jetstack 버전)
를 활용한 방법을 아래에 정리해 둔다.

 


Cert-manager 활용 - jetstack/cert-manager with Traefik v2

  • 사전 요건(Pre-requisites)
    • 사용 가능한 FQDN(여기서는 route53.kube.click을 사용)과 외부 접속 가능한 공인(Public) IP
    • CRD(Custom Resource Definition) 적용 가능한 Kubernetes 환경(여기서는 Local, on-premise kubernetes cluster)
    • Kubernetes 1.15 버전 이상 전제(그 이전 버전은 아래 Reference 4 번 째 내용 참조) 

1. Install Traefik v2 & set-up DNS hosted zone

Traefik ingress 를 제공하는 Helm chart는 stable/traefik(v1)과 traefik/traefik(v2) 가 대표적이다. 여기서는 그 중 traefik/traefik 차트를 사용할 것이다. 상대적으로 Nginx ingress controller나 stable/traefik 보다는 통합적이고 단순하면서도 미래 지향적인 아키텍처와 향상된 기능, 다양한 provider들(Docker, Kubernetes, Rancher, Marathon, AWS ECS 등)을 지원하는 장점을 보인다는 정도로 이해하자.

최근 출시된 것이라 당연한 말이겠지만, 앞 선 다른 Ingress controller 들에 비해 가장 최근에 선보인 것으로 2019년 9월에 GA(Generally Available)되었고, Containous Traefiklabs에서 traefik proxy라는 이름으로 정식 출시되었다.

 

Traefik v2를 위한 다양한 옵션들이 존재하는데, 자세한 사항은 github.com/traefik/traefik/ 을 참고하도록 하고 다음과 같이 traefik/values.yaml 파일을 하나 작성 해 둔다.

additionalArguments:
  - "--accesslog=true"
  - "--accesslog.format=json"
  - "--log.level=INFO"
  - "--entrypoints.websecure.http.tls"
  - "--providers.kubernetesIngress.ingressClass=traefik-cert-manager"
  - "--ping"
  - "--metrics.prometheus"
  - "--api.dashboard=true"
  - "--providers.kubernetescrd"
  - "--providers.kubernetesingress"

deployment:
  replicas: 2

 

Helm v3를 사용해서 traefik 네임스페이스에 Containous helm chart를 통해 traefik을 설치한다. 단, 이 방식으로 설치하면 Traefik 서비스의 Type이 LoadBalancer 방식으로 결정되며, 이 서비스에 외부에서 접속 가능한 공인 IP가 자동 할당된다.

$ helm repo add traefik https://containous.github.io/traefik-helm-chart
$ helm repo update

$ kubectl create namespace traefik
$ helm install --namespace traefik traefik traefik/traefik --values traefik/values.yaml

 

다음 과정에서 A record의 IP 주소로 등록하기 위해 Traefik 서비스의 External IP를 확인한다(여기서는 보안상 실제 IP를 redact 하여 20X.XXX.XXX.XX5 로 표현한다).

$ kubectl get service -n traefik
----------
NAME      TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)                      AGE
traefik   LoadBalancer   172.20.56.175   20X.XXX.XXX.XX5   80:32522/TCP,443:32428/TCP   3d7h

 

본 과정에서 사용할 DNS(Route53) 설정으로 가서 route53.kube.click 서브도메인 설정을 다음 캡처 화면과 같이 Wildcard DNS 방식으로 설정하여 준비해 둔다. AWS, GCP 등에서 서브도메인 설정 방법이 궁금하다면 지난 포스팅 bryan.wiki/305 를 참고해보자.

Route53 서브도메인의 호스팅영역 세부정보

 

DNS의 설정이 끝났으면 host 명령을 통해 외부 접속이 가능한지 확인한다.

$ host route53.kube.click
----------
route53.kube.click has address 2XX.XXX.XXX.XX5

$ host svc.route53.kube.click
----------
svc.route53.kube.click has address 2XX.XXX.XXX.XX5

2. Access to the traefik dashboard

후반으로 가면 Dashboard 접근을 위해 Ingressroute를 생성하고 basic_auth로 로그인 가능한 작업이 진행될 테지만 여기서는 port-forward로 traefik pod의 dashboard UI로 직접 접근이 가능한지 우선 확인해 보자.

Tcp port 9000은 traefik pod의 dashboard UI에 연결되도록 설정된 것이다
# Choose only 1 pod, 'cause we deployed it with replicas=2
$ kubectl get pods -n traefik --selector "app.kubernetes.io/name=traefik" --output=name
----------
pod/traefik-68f54c4b94-cjrrl
pod/traefik-68f54c4b94-w8tnr

$ kubectl port-forward -n traefik pod/traefik-68f54c4b94-cjrrl 9001:9000

이제 웹브라우저를 통해 http://127.0.0.1:9001/dashboard/ 로 접속이 가능할 것이다. 

 

참고로 위의 port 9000은, traefik pod의 dashboard UI에 연결되도록 설정된 것으로, 해당 포트를 알아 내려면 'kubectl get pod -n traefik traefik-68f54c4b94-cjrrl -o json' 와 같이 Pod 구성 전체를 보거나, 또는 아래와 같이 해당 port 를 jsonpath 조회를 통해 직접 찾아낼 수 있다. 
kubectl get pod -n traefik traefik-68f54c4b94-cjrrl -o \
  jsonpath='{.spec.containers[0].ports[?(@.name=="traefik")].containerPort}'

3. Install demo whoami application

whoami 네임스페이스를 만들고 그 내부에 whoami app 리소스를 create(apply)한다.

SSL verify 가 아직 되지 않았기 때문에, curl을 통해 접속해 볼 때는 '--insecure' 또는 '-k' 옵션을 사용해야 한다.
$ kubectl create namespace whoami
$ cat << EOF > whoami-svc-ingress.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
  name: whoami
  namespace: whoami
spec:
  replicas: 1
  selector:
    matchLabels:
      app: whoami
  template:
    metadata:
      labels:
        app: whoami
    spec:
      containers:
        - name: whoami
          image: containous/whoami
          imagePullPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
  name: whoami
  namespace: whoami
  labels:
    app: whoami
spec:
  type: ClusterIP
  ports:
    - port: 80
      name: whoami
  selector:
    app: whoami
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: whoami
  namespace: whoami
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`whoami.route53.kube.click`)
      kind: Rule
      services:
        - name: whoami
          port: 80
  tls:
    secretName: whoami-cert-tls
EOF

$ kubectl apply -f whoami-svc-ingress.yaml
$ curl http://whoami.route53.kube.click/
----------
404 page not found

$ curl https://whoami.route53.kube.click/
----------
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

$ curl --insecure https://whoami.route53.kube.click/
----------
Hostname: whoami-5c8d94f78-xqzvp
IP: 127.0.0.1
IP: 172.21.195.33
RemoteAddr: 172.21.195.9:57340
GET / HTTP/1.1
Host: whoami.route53.kube.click
User-Agent: curl/7.64.1
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 2XX.XXX.XXX.XX3
X-Forwarded-Host: whoami.route53.kube.click
X-Forwarded-Port: 443
X-Forwarded-Proto: https
X-Forwarded-Server: traefik-64b966b4b7-8xh28
X-Real-Ip: 2XX.XXX.XXX.XX3

4. Install cert-manager

# Install the CustomResourceDefinition resources separately
$ kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.0.2/cert-manager.crds.yaml

# Create the namespace for cert-manager
$ kubectl create namespace cert-manager

# Add the Jetstack Helm repository
$ helm repo add jetstack https://charts.jetstack.io

# Update your local Helm chart repository cache
$ helm repo update

# Install the cert-manager Helm chart
$ helm install \
    cert-manager jetstack/cert-manager \
    --namespace cert-manager \
    --version v1.0.2

# Verify installation
$ kubectl get pods --namespace cert-manager
----------
NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-7cdc47446d-z99s6              1/1     Running   0          3d10h
cert-manager-cainjector-6754f97f69-58qsr   1/1     Running   0          3d10h
cert-manager-webhook-7b56df6ddb-zkpx2      1/1     Running   0          3d10h

5. Create cluster issuer + certificate for whoami.route53.kube.click

약간의 시간이 지난 후, ACME solver Pod(cm-acme-http-solver-xxxxx)가 생성되어 cert-manager를 통해
Let's Encrypt 로부터 certificate를 발급받는 과정이 자동으로 진행된다(Log 마지막의 Error 는 Pod가 종료되면서
나타나는 정상적인 상황이므로 무시해도 된다
$ cat << EOF > whoami-cluster-issuer.yaml
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging-issuer-whoami
spec:
  acme:
    email: my@email.id
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      # Secret resource used to store the account's private key.
      name: letsencrypt-staging-whoami
    solvers:
      - selector:
          dnsNames:
            - whoami.route53.kube.click
        http01:
          ingress:
            class: traefik-cert-manager
---
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod-issuer-whoami
spec:
  acme:
    email: my@email.id
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      # Secret resource used to store the account's private key.
      name: letsencrypt-prod-whoami
    solvers:
      - selector:
          dnsNames:
            - whoami.route53.kube.click
        http01:
          ingress:
            class: traefik-cert-manager
EOF

cat << EOF > whoami-certificate.yaml
---
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: whoami-cert
  namespace: whoami
  annotations:
    cert-manager.io/issue-temporary-certificate: "true"
spec:
  commonName: whoami.route53.kube.click
  secretName: whoami-cert-tls
  dnsNames:
    - whoami.route53.kube.click
  issuerRef:
    name: letsencrypt-prod-issuer-whoami
    kind: ClusterIssuer
EOF

$ kubectl apply -f whoami-cluster-issuer.yaml
$ kubectl apply -f whoami-certificate.yaml
$ kubectl logs -n whoami cm-acme-http-solver-xxxxx
----------
...
I0928 16:20:08.132986       1 solver.go:72] cert-manager/acmesolver "msg"="comparing host" "base_path"="/.well-known/acme-challenge"  "host"="whoami.route53.kube.click" "path"="/.well-known/acme-challenge/ZzsXXXdw" "token"="ZzsXXXdw" "expected_host"="whoami.route53.kube.click"

I0928 16:20:08.133026       1 solver.go:79] cert-manager/acmesolver "msg"="comparing token" "base_path"="/.
well-known/acme-challenge" "host"="whoami.route53.kube.click" "path"="/.well-known/acme-challenge/ZzsXXXdw" "token"="ZzsXXXdw" "expected_token"="ZzsXXXdw"

I0928 16:20:08.133049       1 solver.go:87] cert-manager/acmesolver "msg"="got successful challenge request, writing key" "base_path"="/.well-known/acme-challenge" "host"="whoami.route53.kube.click" "path"="/.well-known/acme-challenge/ZzsXXXdw" "token"="ZzsXXXdw"

Error: http: Server closed
Usage:
  acmesolver [flags]
Flags:
      --domain string     the domain name to verify
  -h, --help              help for acmesolver
      --key string        the challenge key to respond with
      --listen-port int   the port number to listen on for connections (default 8089)
      --token string      the challenge token to verify against
http: Server closed

 

whoami 네임스페이스 내에 cert-manager 를 통해 생성되는 주요 resource 들이 정상 상태로 관찰되는지 확인한다.
$ kubectl describe certificate -n whoami whoami-cert
$ kubectl describe certificaterequest -n whoami whoami-cert-abcde
$ kubectl describe order -n whoami whoami-cert-abcde-1234567890

 

SSL 인증키가 Let's Encrypt 로부터 정상 발급되어 조회되는지 확인한다. 정상 발급이 되지 않았거나 위의 발급 요청 과정을 수행하기 전에는 'Issuer: CN=TRAEFIK DEFAULT CERT' 와 같은 결과가 나올 것이다.
$ echo | openssl s_client -showcerts -servername whoami.route53.kube.click -connect whoami.route53.kube.click:443 2> /dev/null | openssl x509 -inform pem -text | grep 'Issuer'
----------
        Issuer: C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X3
                CA Issuers - URI:http://cert.int-x3.letsencrypt.org/

 

'--insecure' 옵션 없이 https 접속이 가능한지 확인한다. 웹 브라우저로도 확인해 보자. 웹주소 왼 쪽에 자물쇠 모양이 나온다면 정상.
$ curl https://whoami.route53.kube.click/
----------
Hostname: whoami-5c8d94f78-44v6m  
IP: 127.0.0.1  
IP: 172.21.195.128  
RemoteAddr: 172.21.195.137:43042  
GET / HTTP/1.1  
Host: whoami.route53.kube.click  
User-Agent: curl/7.64.1  
Accept: _/_  
Accept-Encoding: gzip  
X-Forwarded-For: 2XX.XXX.XXX.XX3  
X-Forwarded-Host: whoami.route53.kube.click  
X-Forwarded-Port: 443  
X-Forwarded-Proto: https  
X-Forwarded-Server: traefik-55c75f88-p97qd  
X-Real-Ip: 2XX.XXX.XXX.XX3

6. Cleanup

$ kubectl delete -f whoami-cluster-issuer.yaml
$ kubectl delete -f whoami-svc-ingress.yaml
$ helm uninstall cert-manager --namespace cert-manager
$ kubectl delete -f https://github.com/jetstack/cert-manager/releases/download/v1.0.2/cert-manager.crds.yaml
$ helm uninstall --namespace traefik traefik
$ kubectl delete namespace traefik
$ kubectl delete namespace cert-manager

References:

doc.traefik.io/traefik/

 

Traefik

 Welcome Traefik is an open-source Edge Router that makes publishing your services a fun and easy experience. It receives requests on behalf of your system and finds out which components are responsible for handling them. What sets Traefik apart, beside

doc.traefik.io

community.traefik.io/t/traefik-v2-helm-a-tour-of-the-traefik-2-helm-chart/6126 

 

Traefik v2 & Helm - A tour of the Traefik 2 Helm Chart

HemChart & Traefik Hello everyone, and welcome to our quick tour of the Traefik 2 Helm Chart, my favorite way of installing Traefik on Kubernetes. Today, we'll walk you through common scenarios to get you started. By the end of the article, you'll have a f

community.traefik.io

medium.com/dev-genius/quickstart-with-traefik-v2-on-kubernetes-e6dff0d65216

 

Quickstart with Traefik v2 on Kubernetes

5-minute setup of Traefik, Let’s Encrypt, and Cloudflare

medium.com

cert-manager.io/docs/installation/kubernetes/

 

Kubernetes

cert-manager runs within your Kubernetes cluster as a series of deployment resources. It utilizes CustomResourceDefinitions to configure Certificate Authorities and request certificates. It is deployed using regular YAML manifests, like any other applicati

cert-manager.io

 

- Barracuda -