k8sでcert-managerとexternal-dnsとIngressを組み合わせて速でサービス公開する

最近社内でもっぱらk8sを触っているなかで社内で雑にコンテナ立てるだけで https://example.com とか公開できるようにした。

external-dns

まずは、名前解決ができるようにするのと、cert-managerを利用してLet’sEncryptでワイルドカード証明書を払い出すために、Route53を利用するためにexternal-dnsをたてた。

マニフェストは雑にこんな感じ。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: external-dns
rules:
- apiGroups: [""]
  resources: ["services"]
  verbs: ["get","watch","list"]
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get","watch","list"]
- apiGroups: ["extensions"]
  resources: ["ingresses"]
  verbs: ["get","watch","list"]
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["list","watch"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: external-dns-viewer
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: external-dns
subjects:
- kind: ServiceAccount
  name: external-dns
  namespace: example-system
---
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: external-dns
spec:
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: external-dns
    spec:
      serviceAccountName: external-dns
      containers:
      - name: external-dns
        image: registry.opensource.zalan.do/teapot/external-dns:v0.5.14
        args:
        - --source=service
        - --source=ingress
        - --domain-filter=<your domain>
        - --provider=aws
        - --policy=upsert-only
        - --registry=txt
        - --txt-owner-id=<your id>
        env:
        - name: AWS_ACCESS_KEY_ID
          valueFrom:
            secretKeyRef:
              name: external-dns
              key: aws_access_key_id
        - name: AWS_SECRET_ACCESS_KEY
          valueFrom:
            secretKeyRef:
              name: external-dns
              key: aws_secret_access_key

Route53にアクセスするための権限が必要なので、ユーザーを払い出して、下記の権限を与えておいてください。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "route53:ChangeResourceRecordSets",
            "Resource": [
                "arn:aws:route53:::hostedzone/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": "route53:ChangeResourceRecordSets",
            "Resource": [
                "arn:aws:route53:::hostedzone/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": "route53:GetChange",
            "Resource": [
                "arn:aws:route53:::change/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "route53:ListHostedZones",
                "route53:ListHostedZonesByName",
                "route53:ListResourceRecordSets"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

cert-manager

cert-managerはhelmを利用しました。こんな感じでシュッと入ります。

$ helm install \
    --name cert-manager \
    --namespace <your-namespace> \
    stable/cert-manager

次にcert-managerが利用するCRDをデプロイします。これはGithubの最新パージョンを利用します。

$ kubectl -n <your-namespace> apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.6/deploy/manifests/00-crds.yaml

最後に、証明書の設定をCRDを利用して行います。

apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
  name: letsencrypt
spec:
  acme:
    email: admin@example.com
    # staging:https://acme-staging-v02.api.letsencrypt.org/directory
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-private-key
    dns01:
      providers:
      - name: route53
        route53:
          accessKeyID: xxxxxxxxxxxxxxxxxxxxxxxxx
          region: ap-northeast-1
          secretAccessKeySecretRef:
            name: external-dns
            key: aws_secret_access_key

実際に証明書を発行するには下記のリソースをapplyしてください。

apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
  name: wildcard-example.com
spec:
  acme:
    config:
    - dns01:
        provider: route53
      domains:
      - '*.example.com'
  commonName: '*.example.com'
  issuerRef:
    kind: ClusterIssuer
    name: letsencrypt
  secretName: cert-wildcard-example

発行状況は下記のコマンドで確認可能です。

$ kubectl describe cert wildcard-example.com

Ingressから利用する

Ingressのインストールは このあたりが参考になります。Ingressをデプロイしたら、あとはこのように利用できます。

---
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: hello-world-deployment
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: hello-world
    spec:
      containers:
        - image: "strm/helloworld-http"
          imagePullPolicy: Always
          name: hello-world-container
          ports:
            - containerPort: 80

---
apiVersion: v1
kind: Service
metadata:
  name: hello-world-svc
  annotations:
    external-dns.alpha.kubernetes.io/hostname: example.com

spec:
  type: ClusterIP
  ports:
     -  port: 8080
        protocol: TCP
        targetPort: 80
  selector:
    app: hello-world
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: hello-world-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.org/ssl-services: "hello-world-svc"
    ingress.kubernetes.io/ssl-redirect: "false"
spec:
  tls:
  - hosts:
    - example.com
    secretName: cert-wildcard-example
  rules:
  - host: example.com
    http:
      paths:
      - path: /
        backend:
          serviceName: hello-world-svc
          servicePort: 8080

ペパボのように多くの商材がある会社では、このようにIngressを作っておくだけで簡単にサービスを公開できるので、社内システムなどすぐにレバレッジを効かせることができます。しかもhttpsな通信でセキュアだしお得。

最後に

最近はこういうことを全社でバーーーンとやれるように @r_takaishi とあれこれやっている。これからいよいよ既存サービスやOpenStack、AWSとk8sを組み合わせて移行したり、新しいものを作っていくフェーズなので誰か一緒にやりましょう。