Nginx ingress controller と External-DNS と CoreDNS(+etcd) を入れて Ingress の Host を DNS へ自動登録して名前解決できる環境構築

## はじめに
オンプレ Kubernetes 上で、Ingress を作成すると自動で Ingress リソースに記載した VirtualHost を DNS に登録し、名前解決ができる環境を構築した

Ingress も初めてだったため、Nginx ingress controller の導入から記載している

## 実行環境
### Kubernetes クラスタ
kubespray で構築した オンプレ kuberntes クラスタ
Versoin: 1.16.3

詳細は下記
https://qiita.com/suzuyui/items/e7531fe5e1e84c061b23

Metallb で Laodbalancer 構築済
詳細は下記
https://qiita.com/suzuyui/items/c83554dd055ef0f4253a

### Docker サーバ
coredns + etcd は今回は簡易的に Docker でのシングルで構築
kubernetes 化、クラスタ化は別途検討

OS: CentOS 8.0.1905 
Docker-CE: 18.06.3-ce
docker-compose: 1.25.0

サーバIP: 192.168.129.16 
※以下、この IP で記載しているので注記

### DNS フォワーダ
EdgeRouter X という Vyatta ベースの OS で動作する Router で、 DNS フォワーダとしても動作させている (今回確認のデスクトップもこちらを参照させている)
今回の Ingress 用のドメインを、CoreDNS の方を参照させるように動作させる

Edgeroute-Xサイト： https://www.ui.com/edgemax/edgerouter-x/


## 概要説明
全体の構成概要を図示すると下記のような感じになる (オレンジ部分が今回記載箇所)
ローカルドメインには `.home` を使用する

![ingress_externaldns_coredns_連携概要図_suzuyu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/adebc55d-f271-9520-a156-086f8b9bce49.png)


### Nginx ingress controller 
Kubernetes は Ingress Controller をデフォルトでは実装していないため、オンプレでの Kubernetes では追加で導入する必要がある。
また、実装によって動作も異なる。

今回は、Kubernetes リポジトリ配下の NGINX Ingress Controller を使用する。
※その他に NGINX, Inc. が公開している [NGINX, NGINX Plus を利用する Contoroller](https://github.com/nginxinc/kubernetes-ingress) もある

サイト： https://kubernetes.github.io/ingress-nginx/
Github: https://github.com/kubernetes/ingress-nginx

#### リソース概要
リソースの概要図を記載すると下記の通り。
IP については固定 IP をアサインしておきたかったので、その箇所だけ追記している。(赤字)
![nginx_controller.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/d7b854a2-d2a2-6d39-422e-d8b161764c4a.png)

マニフェストは ２ つで下記の通り。

![nginx_controller_manifest.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/14a7e497-4da0-9dcb-05be-51e33afb539d.png)

### External-DNS, CoreDNS + Etcd
#### External-DNS
Ingress 等のリストを取得して、外部公開するホストを DNS へ登録してくれる。

対応している DNS は様々で、対応状況は、`Google Cloud DNS` と `AWS Route53` は `Stable` だが、それ以外は `Beta` or `Alpha` となる。
※オンプレ実装可能なものは `Alpha` しかない
※ 2020.03.23時点

今回は、External-DNS が対応可能な `CoreDNS` を選択した。
External-DNS は実際には Etcd へ登録を実施するため、CoreDNS のバックエンドとして `Etcd` を導入する。

Github: https://github.com/kubernetes-sigs/external-dns

#### CoreDNS + Etcd
Go で書かれた DNS で、CNCF graduated project の 1 つ。
権威サーバ、リゾルバサーバ両方として動作可能。
レコードのバックエンドとしては、`Etcd`, `hosts`、その他 Plugin など様々なものをとすることができる。

今回は External-DNS を使用するので、 `Etcd` をバックエンドとして採用する

サイト: https://coredns.io/
Github: https://github.com/coredns/coredns

#### リソース・構成概要図
構成概要図は下記の通り。

 - External-DNS 
    - kubernetes クラスタ上に構築
 - CoreDNS 
    - Docker サーバ上に構築
 
![externaldns_coredns_概要_suzuyu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/f99f8c72-d220-c5ce-61df-7cd54b47e239.png)

設定は下記の通り 3 ファイル(赤文字)で、詳細は別途後述。

![externaldns_coredns_概要_manifest_suzuyu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/f43f4045-c3e2-2e20-3b71-f587aca5b3ac.png)


## 構築ステップ

次からの構築は下記ステップで記載していく。

1. Nginx ingress controller / Ingress Serivce 構築
2. CoreDNS(+Etcd) 構築
3. External-DNS 構築
4. DNSフォワーダ設定 (EdgeRouter X)

構築後に実際に Ingress を作成して、名前解決および URL アクセスも確認する。

## 1. Nginx ingress controller / Ingress Serivce 構築

### Nginx ingress controller

公式からコントローラ用のマニフェストを落としてきて apply する。

```
$ curl -OL https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/mandatory.yaml
$ kubectl apply -f mandatory.yaml
namespace/ingress-nginx created
configmap/nginx-configuration created
configmap/tcp-services created
configmap/udp-services created
serviceaccount/nginx-ingress-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created
role.rbac.authorization.k8s.io/nginx-ingress-role created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding created
deployment.apps/nginx-ingress-controller created
limitrange/ingress-nginx created
```

参考： https://kubernetes.github.io/ingress-nginx/deploy/


### Ingress Service 

公式から ingress-nginx Service 用のマニフェストを落としてきて、IP 設定だけ追加して apply する。

```
$ curl -OL https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/cloud-generic.yaml
```

Ingress の IP を固定 IP とする場合は、1行追加する (固定 IP 不要の場合は、手を加えなくても良い)

```yaml
  loadBalancerIP: [固定IP]
```

下記は、上記の一行を追加した例。

```yaml:cloud-generic-ipfix.yaml
kind: Service
apiVersion: v1
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  externalTrafficPolicy: Local
  type: LoadBalancer
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
  ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: http
    - name: https
      port: 443
      protocol: TCP
      targetPort: https
  loadBalancerIP: 192.168.131.80

---
```

apply する。

```
$ kubectl apply -f cloud-generic-ipfix.yaml
service/ingress-nginx created
```

nginx-ingress コントローラが稼働していることを確認する。

```
$ kubectl get pods --all-namespaces -l app.kubernetes.io/name=ingress-nginx
NAMESPACE       NAME                                        READY   STATUS    RESTARTS   AGE
ingress-nginx   nginx-ingress-controller-7f74f657bd-mcfdx   1/1     Running   0          44m
```

## 2. CoreDNS(+Etcd) 構築

CoreDNS と Etcd を Docker-compose で構築する。

下記は docker-compose.yml 例
コンテナイメージはどちらも公式のものを使用した。

```docker-compose.yml
---
version: '3.5'
services:
  coredns:
    container_name: coredns
    image: coredns/coredns:1.6.7
    restart: on-failure
    ports:
      - '53:53'
      - '53:53/udp'
    volumes:
      - './config:/etc/coredns'
    command: -conf /etc/coredns/Corefile
  etcd:
    container_name: etcd
    image: quay.io/coreos/etcd:v3.4.5
    ports:
    - '2379:2379'
    - '2380:2380'
    - '4001:4001'
    volumes:
    - etcddata01:/etcd-data
    command:
    - /usr/local/bin/etcd
    - --data-dir=/etcd-data
    - --name=node1
    - --listen-peer-urls=http://0.0.0.0:2380
    - --advertise-client-urls=http://0.0.0.0:2379,http://0.0.0.0:4001
    - --listen-client-urls=http://0.0.0.0:2379,http://0.0.0.0:4001

volumes:
  etcddata01:
    driver: local
```

Corefile にドメインの設定を記載する。
下記例は、`ingress.home`を ingress で設定するドメイン名として記載している。
コメントしている範囲は、CoreDNS を DNS フォワーダとしても使用したい場合は、コメントを解除する。

```txt:config/Corefile
# .:53 {
#     forward . 8.8.8.8 9.9.9.9
#     log
#     errors
# }

ingress.home:53 {
    etcd ingress.home {
        path /skydns
        endpoint http://192.168.129.16:2379
    }
    log
    errors
}
```

docker-compose up -d で立ち上げる

```
# docker-compose up -d
Creating network "coredns_default" with the default driver
Creating coredns ... done
Creating etcd    ... done

# docker-compose ps
 Name                Command               State                                   Ports                                 
-------------------------------------------------------------------------------------------------------------------------
coredns   /coredns -conf /etc/coredn ...   Up      0.0.0.0:53->53/tcp, 0.0.0.0:53->53/udp                                
etcd      /usr/local/bin/etcd --data ...   Up      0.0.0.0:2379->2379/tcp, 0.0.0.0:2380->2380/tcp, 0.0.0.0:4001->4001/tcp
```

## 3. External-DNS 構築

External-DNS を構築する。

下記は公式にあった [Minikube での実施例](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/coredns.md#manifest-for-clusters-with-rbac-enabled)で書いてあったものを一部手を加えたもの。

`env:` の `ETCD_URLS` には CoreDNS の Etcd の IP:Port を記載する。

```external-dns.yaml
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: external-dns
rules:
- apiGroups: [""]
  resources: ["services","endpoints","pods"]
  verbs: ["get","watch","list"]
- apiGroups: ["extensions"]
  resources: ["ingresses"]
  verbs: ["get","watch","list"]
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["list"]
---
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: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-dns
  namespace: kube-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: external-dns
  namespace: kube-system
spec:
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: external-dns
  template:
    metadata:
      labels:
        app: external-dns
    spec:
      serviceAccountName: external-dns
      containers:
      - name: external-dns
        image: bitnami/external-dns:latest
#        image: registry.opensource.zalan.do/teapot/external-dns:latest
        args:
        - --source=ingress
        - --provider=coredns
#        - --log-level=debug # debug only
        env:
        - name: ETCD_URLS
          value: http://192.168.129.16:2379

```

上記 yaml を apply する。

```
$ kubectl apply -f external-dns.yaml 
```

参考： https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/coredns.md#manifest-for-clusters-with-rbac-enabled


## 4. DNSフォワーダ設定 (EdgeRouter X)

EdgeRouter X で DNS のフォワーディングオプションを利用して、`.home` ドメインは CoreDNS サーバへフォワードする設定をする。
下記は、設定例。

```
set service dns forwarding name-server 8.8.8.8
set service dns forwarding name-server 8.8.8.4
set service dns forwarding options server=/home/192.168.129.16
```

参考： https://help.ubnt.com/hc/en-us/articles/115010913367-EdgeRouter-DNS-Forwarding-Setup-and-Options


## 動作確認: Ingress 作成と名前解決でのアクセス
Prometheus / Grafana / AlertManager を Ingress で公開する場合で、動作確認を実施した。

### Ingress 作成

Ingress 作成例は下記の通り。

Prometheus / Grafana / AlertManager の host 登録と、host指定無しの設定を記載している。
host 指定無しは IP でのアクセスで使用できる。 (下記テストでは Grafana を指定した)

```prometheus-ingress.yaml
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: prometheus
  namespace: monitoring
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: "nginx"
    nginx.com/health-checks: "true"
spec:
  rules:
  - host: grafana.ingress.home
    http:
      paths:
      - path: /
        backend:
          serviceName: grafana
          servicePort: 3000
  - host: alertmanager.ingress.home
    http:
      paths:
      - path: /
        backend:
          serviceName: alertmanager-main
          servicePort: 9093
  - host: prometheus.ingress.home
    http:
      paths:
      - path: /
        backend:
          serviceName: prometheus-k8s
          servicePort: 9090
  - http:
      paths:
      - path: /
        backend:
          serviceName: grafana
          servicePort: 3000
```

上記の `serviceName` は Service の NAME を指定している。
下記は、実施環境での Service Name での表示例。

```
$ kubectl get svc -n monitoring
NAME                    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
alertmanager-main       ClusterIP   10.233.48.158   <none>        9093/TCP                     2d16h
grafana                 ClusterIP   10.233.55.79    <none>        3000/TCP                     2d16h
prometheus-k8s          ClusterIP   10.233.4.237    <none>        9090/TCP                     2d16h
```

マニフェストを apply する。
`ADDRESS` の割り当てには少し時間がかかる場合がある。

```
$ kubectl apply -f prometheus-ingress.yaml
ingress.networking.k8s.io/prometheus created

$ kubectl get ingress -n monitoring
NAME         HOSTS                                                                                ADDRESS          PORTS   AGE
prometheus   grafana.ingress.home,alertmanager.ingress.home,prometheus.ingress.home + 1 more...   192.168.131.80   80      2m20s
```

### アクセス確認
dig で名前解決確認 (フォワーダ経由) と、ブラウザでの URL アクセス確認を実施した。
#### dig
dig で問合せをして、 A レコードが返ってくることが確認できる。
※ 192.168.129.254 は EdgeRouter X の IP アドレス

```
$ dig -p 53 @192.168.129.254 A grafana.ingress.home +noall +answer

; <<>> DiG 9.11.3-1ubuntu1.11-Ubuntu <<>> -p 53 @192.168.129.254 A grafana.ingress.home +noall +answer
; (1 server found)
;; global options: +cmd
grafana.ingress.home.	300	IN	A	192.168.131.80

$ dig -p 53 @192.168.129.254 A prometheus.ingress.home +noall +answer

; <<>> DiG 9.11.3-1ubuntu1.11-Ubuntu <<>> -p 53 @192.168.129.254 A prometheus.ingress.home +noall +answer
; (1 server found)
;; global options: +cmd
prometheus.ingress.home. 300	IN	A	192.168.131.80

$ dig -p 53 @192.168.129.254 A alertmanager.ingress.home +noall +answer

; <<>> DiG 9.11.3-1ubuntu1.11-Ubuntu <<>> -p 53 @192.168.129.254 A alertmanager.ingress.home +noall +answer
; (1 server found)
;; global options: +cmd
alertmanager.ingress.home. 300	IN	A	192.168.131.80
```

#### ブラウザ
各 URL をブラウザでアクセスして、正しくアクセスできることが確認できる。

`http://grafana.ingress.home` でのアクセス
![スクリーンショット 2020-03-23 13.05.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/64dc8669-c7cb-94ab-fde3-d4cb9c7555fd.png)

`http://prometheus.ingress.home` でのアクセス
![スクリーンショット 2020-03-23 13.06.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/c976c8d4-5aa5-0b4e-f6de-9e7cb7ef0596.png)

`http://alertmanager.ingress.home` でのアクセス
![スクリーンショット 2020-03-23 13.06.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/92d3c36d-b48b-de19-47a9-086d922bfcf3.png)

`http://192.168.131.80` でのアクセス
![スクリーンショット 2020-03-23 13.25.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/a8a3b258-135a-704c-e0bb-fbb45cf1ab0f.png)


## おわりに

Ingress を作成すると、自動で名前解決でアクセスができる環境まで構築ができた

今後、Ingress は TLS 終端や、認証などできることは多岐に渡るので試していきたい。
また、CoreDNS / Etcd についても [etcd-operator](https://github.com/coreos/etcd-operator) などで冗長化・Kubernetes 化も実施していきたい。