nginx-ingressを複数系統で構築する (with cert-manager/dns-01チャレンジ)

悲しみに終わった前回（[nginx-ingress/kube-legoを複数系統で構築する](https://qiita.com/yokomotod/items/ac91acfb39cd1222d3bb) ）に対しての本命。

DEPRECATEDなkube-legoに代わって同じ開発元による後継、[cert-manager](https://github.com/jetstack/cert-manager)を使う。

# TL; DR

cert-manager/DNS-01チャレンジ方式を使うという選択さえしてしまえば、
nginx-ingressを複数系統で構築することにもはや嵌りそうな罠もなにもなかった。

**DNS-01最高**

NGINX Ingress ControllerだろうがGCE Ingress Controllerだろうが、
複数系統だろうがそうでなかろうが、とにかくHTTP-01に比べて**DNS-01最高**。

（HTTP-01と違って[使える環境の制約](https://github.com/jetstack/cert-manager/blob/master/docs/api-types/issuer/spec.md#supported-dns01-challenge-providers)があるけど）


# やったこと

[NGINX Ingress Controller](https://github.com/kubernetes/ingress-nginx)を使うと、
各IngressでそのコントローラのService(type: LoadBalancer)を共有する形になり、GKEでいうとTCP LoadBalancerを共有し、IPも同じになる模様。

(それによるデメリットがどれほどかは明確にわかってない）

それを避けるために複数のNGINX Ingress Controllerをデプロイして系統を分ける。

その際、cert-managerのDNS-01チャレンジ方式を使ってLet's EncryptのSSL証明書の管理を自動化。

cert-managerでは `Issuer`(namespaceごとに配備)を使うか`ClusterIssuer`(namespace無しでcluster全体に配備)を使うか選べる。
複数namespaceを一括で賄いたいので`ClusterIssuer`を使った。

やっぱり基本的にHelmを使う。


# 環境

- Host: GKE
- Master version/Node version: 1.8.5-gke.0
- Helm: v2.8.0
- chart: stable/nginx-ingress:0.9.1
- chart: [github.com/jetstack/cert-manager:v0.2.3](https://github.com/jetstack/cert-manager/tree/v0.2.3/contrib/charts/cert-manager)
- image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.10.2
- image: quay.io/jetstack/cert-manager-controller:v0.2.3

# 構成

- NGINX Ingress Controller
    - namespace: `ingress-nginx`

- cert-manager
    - namespace: `kube-system`
        - ClusterIssuerが生成するSecretがkube-system namespaceに入るのを回避できなかったので`cert-manager` namespaceにデプロイするのは諦めた

- 通常の系統
    - `nginx-ingress`
- 別系統(社内用系統なイメージ)
    - `nginx-ingress-inhouse`
- **cert-managerは共通で一つ**

# 手順

(helmのセットアップ手順は省略)


## namespace作成

```
# ingress-nginx
curl https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/namespace.yaml \
  | kubectl apply -f -
```

必須ではないけど、公式の命名に準じる。

## nginx-ingressのセットアップ

```
helm install stable/nginx-ingress \
  --name nginx-ingress \
  --namespace ingress-nginx \
  --set controller.replicaCount=2
```

`replicaCount` は任意で。

## nginx-ingress-inhouseのセットアップ

```
helm install stable/nginx-ingress \
  --name nginx-ingress-inhouse \
  --namespace ingress-nginx \
  --set controller.ingressClass=nginx-inhouse \
  --set controller.replicaCount=2
```

`ingressClass` をデフォルトの`nginx`とは別名に指定する。

## cert-managerのセットアップ

[GKE で TLS 証明書を自動管理(cert-manager DNS-01 編)](https://qiita.com/apstndb/items/3a39a1e6acacbbc30765) がとてもわかりやすいのでそちらを参照。

kube-lego/HTTP-01のときと違ってNGINX Ingress Controllerが２つだろうとcert-managerには関係がないので、セットアップの仕方に特に変化はない。

上の記事の手順の中で唯一、 `Issuer` ではなく `ClusterIssuer` が使いたかったのでそこだけ変更

```
$ kubectl apply -f - << EOF
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    # The ACME server URL
    server: https://acme-v01.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: admin@apstndb.net
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-prod
    # Enable the HTTP-01 challenge provider
    http01: {}
    # ACME dns-01 provider configurations
    dns01:
      # Here we define a list of DNS-01 providers that can solve DNS challenges
      providers:
      - name: prod-dns
        clouddns:
          # A secretKeyRef to a the google cloud json service account
          serviceAccountSecretRef:
            name: clouddns-service-account
            key: cert-manager-key.json
          # The project in which to update the DNS zone
          project: apstndb-sandbox
EOF
issuer "letsencrypt-prod" created
```

## nginx-ingress系統にingress追加

例

```
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: web-nginx
  namespace: default
  annotations:
    kubernetes.io/ingress.class: nginx
spec:
  rules:
  - host: example.com
    http:
      paths:
      - path: /
        backend:
          serviceName: web
          servicePort: 80
  tls:
  - hosts:
    - example.com
    secretName: web-tls
```

`name`, `namespace`, `serviceName`, `servicePort` などは適宜命名。
`example.com` は要変更。
`secretName` にはcert-managerの`Certificate`リソースで生成したSecretを指定する。

`kubernetes.io/ingress.class`が`nginx`なので主系統の`nginx-ingress`に捕捉されて処理される。


## nginx-ingress-inhouse系統にingress追加

例

```
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: admin-nginx-inhouse
  namespace: default
  annotations:
    kubernetes.io/ingress.class: nginx-inhouse
spec:
  rules:
  - host: admin.example.com
    http:
      paths:
      - path: /
        backend:
          serviceName: admin-service
          servicePort: 80
  tls:
  - hosts:
    - admin.example.com
    secretName: admin-tls
```

`kubernetes.io/ingress.class`が`nginx-inhouse`なので`nginx-ingress-inhouse`に捕捉される。

## おわり

kube-legoではHTTP-01チャレンジ方式しか使えずIngress側も駆使して証明書取得していたのに比べると、
DNS-01チャレンジではIngressは無関係になって、単に準備されたSecretを使うだけになる。

そのおかげで、NGINX Ingress Controllerが幾つあろうとcert-managerは一つで済み、
設定にも難しいことは特になくなった。 :tada: