はじめての Traefik v2 + cert-manager による https化 奮闘記

## はじめに

本記事は、既に多くの方が書かれているような「これは、こういうものだ」などの説明は ~~詳しくないので~~ 省略しており、難しいことは考えず、流れに沿って淡々と作業をする内容になっています。

ゴールは、AKSのサービスを Let's Encrypt で https 対応することです。

間違いの指摘や、ここはこうした方がいいよなど、いただけると嬉しいです。


## 実行環境
本記事では、以下の環境を使用しました。
異なるバージョンでは、同じ結果が得られない場合がありますが、ご了承ください。

| |Component|Version|
|:---:|:---|:---|
|OS|macOS|Big Sur 11.3.1|
|TOOL|Kubernetes (AKS)|1.19.9|
|TOOL|kubectl|1.19.7|
|TOOL|[Azure CLI](https://docs.microsoft.com/ja-jp/cli/azure/install-azure-cli)|2.21.0|
|TOOL|[kubectx, kubens](https://kubectx.dev/)|0.9.3|
|TOOL|[Helm](https://helm.sh/)|3.5.3|
|CHART|[traefik](https://artifacthub.io/packages/helm/traefik/traefik/9.19.0)|9.19.0|
|CHART|[cert-manager](https://artifacthub.io/packages/helm/cert-manager/cert-manager/1.3.1)|1.3.1|
|CHART|[whoami](https://artifacthub.io/packages/helm/cowboysysop/whoami/2.2.0)|2.2.0|
z
> **注**:
> 記事中の `kc` コマンドは、`kubectl` コマンドのエイリアスです。
> 
> [Newbernetes: kubectl the Kubernetes CLI - Cory O'Daniel
](https://medium.com/coryodaniel/3751fb6e34ca) を参考にしました。

## Let's Encrypt で https 化する
[はじめての AKS によるサービス公開](https://qiita.com/eternity1984/items/ae6e5684fd7b02aa23e4)に分離しました。

上記で作成したサービスを、無料で使えるSSLサーバー証明書 **Let's Encrypt** を使って**HTTPS化**していきます。

### 1. cert-managerを導入する
1.**cert-manager**用の名前空間を作ります。

```bash
# 名前空間の作成
$ kc create ns cert-manager
⥤ namespace/cert-manager created

# 名前空間の切り替え
$ kubens cert-manager
⥤ Context "try-traefik-https" modified.
⥤ Active namespace is "cert-manager".
```

2.**helm** を使って、`cert-manager`をインストールします。

```bash
# リポジトリの追加
$ helm repo add jetstack https://charts.jetstack.io
⥤ "jetstack" has been added to your repositories

# CRDs のインストール
$ kc apply -f https://github.com/jetstack/cert-manager/releases/download/v1.3.1/cert-manager.crds.yaml
⥤ customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created
⥤ customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created
⥤ customresourcedefinition.apiextensions.k8s.io/challenges.acme.cert-manager.io created
⥤ customresourcedefinition.apiextensions.k8s.io/clusterissuers.cert-manager.io created
⥤ customresourcedefinition.apiextensions.k8s.io/issuers.cert-manager.io created
⥤ customresourcedefinition.apiextensions.k8s.io/orders.acme.cert-manager.io created

# jetstack/cert-manager をインストール
$ helm install cert-manager jetstack/cert-manager 
⥤ NAME: cert-manager
⥤ LAST DEPLOYED: Mon May 10 22:33:59 2021
⥤ NAMESPACE: cert-manager
⥤ STATUS: deployed
⥤ REVISION: 1
⥤ TEST SUITE: None
⥤ NOTES:
⥤ cert-manager has been deployed successfully!
⥤ ...
```

3.正常に動作しているか確認します。

```bash
# ポッドの状態
$ kc get po
⥤ NAME                                       READY   STATUS    RESTARTS   AGE
⥤ cert-manager-7998c69865-pct2m              1/1     Running   0          30s
⥤ cert-manager-cainjector-7b744d56fb-spqmg   1/1     Running   0          30s
⥤ cert-manager-webhook-7d6d4c78bc-bg9lv      1/1     Running   0          
30s
```

### 2. DNS-01 チャレンジ (Azure DNS)
それでは、Let's Encryptの証明書を**Azure DNS**で発行できるようにしていきます。

GUI操作が嫌いな人は、以下のコマンドで **6.** から始められます。

```bash
# サービス プリンシパルの作成
$ az ad sp create-for-rbac --name try0ut.ml --years 1 
⥤ ...
⥤ {
⥤   "appId": "430c5a67-157e-4133-89c7-3f974293ea4f",
⥤   "displayName": "try0ut.ml",
⥤   "name": "http://try0ut.ml",
⥤   "password": "dRRzw-gsleqF6BLxdv_d6C.9s5.qjQT04.",
⥤   "tenant": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
⥤ }

# 共同作成者 ロールの割り当てを削除
$ az role assignment delete --assignee 430c5a67-157e-4133-89c7-3f974293ea4f --role Contributor
```

1.**Azure Active Directory**から `アプリの登録` > `新規登録` を選びます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604208/50546acf-03c7-0d8c-0347-15cad6d20328.png)

2.**アプリケーションの登録**の項目を設定した後、`登録`します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604208/e8b365dc-521a-f56f-d0a1-7fd51f410d18.png)

- 名前: `try0ut.ml` (DNS Zone と同じ名前だと分かり易い)
- サポートされているアカウントの種類: `この組織ディレクトリのみに含まれるアカウント（＊＊＊ - シングルテナント）`
- リダイレクト URI: `http://try0ut.ml` (省略でもよい)

3.作成されたサービス プリンシパルの**アプリケーション（クライアント）ID** (e.g. `430c5a67-157e-4133-89c7-3f974293ea4f`) は、後ほど使うのでメモしておきましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604208/a8e2bd7b-969c-9dd7-1edb-cebe5d046ad1.png)

4.**サービス プリンシパル**の `証明書とシークレット` > `新しいクライアント シークレット` を選び、**クライアント シークレット** を追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604208/f8fe2283-d460-4ac9-6e67-6a4f60574896.png)

- 説明: `rbac` (何でも良い)
- 有効期限: `推奨: 6 か月` (何でも良い）

有効期限が短い場合は、クラスター内のクライアントシークレットを定期的に入れ替える必要があります。

5.作成されたクライアント シークレットの**値**をコピーします。(画面移動すると非表示になります。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604208/feca5696-7ea1-2807-74f9-0722a53b54d7.png)

6.クライアント シークレットの値をテキスト ファイル(e.g. `credentials.env`)に保存します。

```shell:credentials.env
client-secret=dRRzw-gsleqF6BLxdv_d6C.9s5.qjQT04.
```

> **Information**:
> 変数名は `client-secret` 以外でも構いませんが、後ほど構成する**ClusterIssuer**の`clientSecretSecretRef`を忘れず書き換えるようにしてください。

7.`cert-manager`名前空間にシークレットを入れておきましょう。

```bash
# 名前空間の切り替え
$ kubens cert-manager
⥤ Context "try-traefik-https" modified.
⥤ Active namespace is "cert-manager".

# シークレットの作成
$ kc create secret generic azuredns-config --from-env-file ./credentials.env
secret/azuredns-config created

# 機密データの破棄
$ rm -f ./credentials.env
```

> **Warning:**
> `--from-literal`を使って直接シークレットを作成してもいいですが、シェルの履歴にパスワードが残ります。

8.**Azure DNS ゾーン**に移動し、`アクセス制御(IAM)` > `追加` > `ロールの割り当ての追加` を選びます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604208/c5822350-10af-45f6-b029-afffe485ef44.png)

9.**ロールの割り当ての追加**にて、作成した**サービス プリンシパル**に`DNS ゾーンの共同作成者`を割り当てます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604208/eddf9f58-ec7e-3371-4e3c-442c6c284db3.png)

10.**ClusterIssuer**を作ります。

```yaml:issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt
spec:
  acme:
    # The ACME server URL
    server: https://acme-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: admin@try0ut.ml
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt
    # Enable the HTTP-01/DNS-01 challenge provider
    solvers:
    - dns01:
        azureDNS:
          # Service principal clientId (also called appId)
          clientID: 430c5a67-157e-4133-89c7-3f974293ea4f
          # The following is the secret we created in Kubernetes. 
          # Issuer will use this to present challenge to Azure DNS.
          clientSecretSecretRef:
            name: azuredns-config
            key: client-secret
          # Azure subscription Id
          subscriptionID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
          # Azure AD tenant Id
          tenantID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
          # ResourceGroup name where dns zone is provisioned
          resourceGroupName: aks-learning
          hostedZoneName: try0ut.ml
          environment: AzurePublicCloud
```

- **email**: `admin@try0ut.ml` (Let's Encrypt の有効期限の通知を受け取るメールアドレス)
- **clientID**: `430c5a67-157e-4133-89c7-3f974293ea4f` (サービスプリンシパルのクライアント ID)
- **clientSecretSecretRef**
  - **name**: `azuredns-config` (シークレット名を変えた場合は変更)
  - **key**: `client-secret` (変数名を変えた場合は変更)
- **subscriptionID**: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` (サブスクリプションIDを設定)
- **tenantID**: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` (Azure AD のテナント IDを設定)
- **resourceGroupName**: `aks-learning` (DNS ゾーンを配置したリソース グループの名前)
- **hostedZoneName**: `try0ut.ml` (DNS ゾーンの名前)

```bash
$ kc apply -f issuer.yaml                                                                   
⥤ clusterissuer.cert-manager.io/letsencrypt created
```

11.**Certificate** を作ります。

```yaml:whoami-cert.yaml
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: whoami-cert
  namespace: whoami
spec:
  commonName: whoami.try0ut.ml
  secretName: whoami-cert
  dnsNames:
    - whoami.try0ut.ml
  issuerRef:
    name: letsencrypt
    kind: ClusterIssuer
```

```bash
$ kc apply -f whoami-cert.yaml 
⥤ certificate.cert-manager.io/whoami-cert created
```

12.**traefik** の `values.yaml` で **tls** を有効にします。

```diff_yaml:values.yaml
- additionalArguments: []
- #  - "--providers.kubernetesingress.ingressclass=traefik-internal"
+ additionalArguments:
+ - "--entrypoints.websecure.http.tls"
```

```bash
# 名前空間の切り替え
$ kubens traefik
⥤ Context "try-traefik-https" modified.
⥤ Active namespace is "traefik".

# 新しい設定値でアップグレード
$ helm upgrade traefik traefik/traefik -f values.yaml 
⥤ Release "traefik" has been upgraded. Happy Helming!
⥤ NAME: traefik
⥤ LAST DEPLOYED: Mon May 10 22:46:11 2021
⥤ NAMESPACE: traefik
⥤ STATUS: deployed
⥤ REVISION: 2
⥤ TEST SUITE: None
```

13.**whoami** の **IngressRoute** を変更します。

```diff_yaml:whoami-ingress.yaml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: whoami
  namespace whoami
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`whoami.try0ut.ml`)
      kind: Rule
      services:
        - name: whoami
          port: 80
+   tls:
+     secretName: whoami-cert
```

```bash
$ kc apply -f whoami-ingress.yaml 
⥤ ingressroute.traefik.containo.us/whoami configured
```

14.Webブラウザーで、https://whoami.try0ut.ml/ にアクセスしてみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604208/14bee673-e3ea-71ce-50d2-fabab49d917e.png)

## おわりに
一部のチュートリアルではバージョンが古いなどの理由から、同じ手順で実行できないこともありました。
`traefik` にも `tls` のオプションがあるようなので、`cert-manager` を使わずに簡単にできるかどうかも今後確認していきます。