How do you like k3s ? - CoreDNSで作るお家DNS Cacheコンテナ

この記事は NTTコミュニケーションズ [Advent Calendar 2020](https://qiita.com/advent-calendar/2020/nttcom) の4日目です。

昨日は@yuki_uchidaさんの[WebSocketの次の技術！？WebTransportについての解説とチュートリアル](https://qiita.com/yuki_uchida/items/d9de148bb2ee418563cf)でした！

# はじめに
## 何している人
社会人7年目。イノベーションセンターという組織でDevOpsやSREに取り組んでいます。
最近ではCloudNative Days Tokyo 2020にて [Cloud Native環境におけるエンタープライズシステムに対する高可用性実現への取り組み] (https://event.cloudnativedays.jp/cndt2020/talks/3) を発表させていただいたり，KubeCon 2020 EUにて [Deliver Your Cloud Native Application with Design Pattern as Code](https://kccnceu20.sched.com/event/ZeiY/deliver-your-cloud-native-application-with-design-pattern-as-code-jun-makishi-rintaro-sekino-ntt-communications) というタイトルで発表させていただいております。

## 今回紹介したいこと
新型ウイルスの影響もあり，多くのエンジニアの方々がリモートワークをされているのではないかと思います。そんな状況だとお家の環境ってとても大切ですよね。今日はそんなお家環境を充実させながらKubernetesを勉強する環境を作りつつ，Raspberry Piなんかを活用しながら，64-bit ARMも遊べる環境をK3sを使って作るお話をしようと思います。

実際にデプロイするアプリ（おうちk3sユースケース！）は，不要な広告をブロックする手作りお家DNS CacheコンテナをCoreDNSを利用して立てますよ！

# K3sとは

まずK3sとは何かについて。Rancher Labs社が発表したOSSで，比較的リソースの少ない環境に対してもインストール可能な軽量Kubernetes Distributionです。利用していて感じる利点は，たった40MB程度のシングルバイナリでKubernetes Master/Nodeを動かせるためRaspberry Piのようなデバイスを利用してKubernetes クラスタが組めることです。また，バイナリの入れ替えでバージョンアップができるためメンテナンスがすごく楽です。AArch64/ARM64 に対応していることも重要で，IoTやEdgeのユースケースに適しています。

Ref: [The Lightweight Kubernetes Distribution Built for the Edge](https://rancher.com/blog/2019/2019-02-26-introducing-k3s-the-lightweight-kubernetes-distribution-built-for-the-edge)

# 構成について

## 物理構成

※ 動作を保証するものではありません。
構成例の1つとして参照していただければと思います。特に電源に関してはRaspberry Pi 4が要求する5V 3Aを満たしていないので気になる方は純正電源やPoEをご検討すると良いと思います。でもこういう構成で利用している人がいるって情報、、、助かりますよね(・∀・) これで1年ほど運用して特に問題は出ておりません。

Raspberry Pi4 ModelB 4GBを4台用意し，1台はMaster，残り3台はNodeとして構成します。
ネットワークを構成するルータやスイッチ等は別途用意する必要があります。
※最近は[8GBのPi 4](https://www.raspberrypi.org/blog/8gb-raspberry-pi-4-on-sale-now-at-75/)も出たので気になる方はそちらも

- Raspberry Pi4 ModelB 4GB ラズベリーパイ4 技適対応品　×　4
- 32GB TOSHIBA 東芝 microSDHCカード CLASS10 UHS-I対応 R:100MB/s　×　4
- SANWA SUPPLY つめ折れ防止カテゴリ7LANケーブル 2m ブラックホワイト　×　4
- Rampow USB Type C ケーブル【2m/二本組/保証付き】急速充電 USB3.0 タイプc ケーブル QuickCharge3.0対応　×　2(2本組を2つでケーブルは4本)
- Anker PowerPort I PD - 1 PD & 4 PowerIQ　×　1
- 積層式ケース for Raspberry Pi 4 / Pi 3 Model B+ 専用 保護用クリア・アクリルケース カバー アルミヒートシンク二個付き Clear/透明 (4段)　×　1

![raspberry pi 4 project.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216192/4ef28d54-5fa1-c3a1-eba4-cd4f34b9bca9.jpeg)

ここにパイがあるじゃろ？

( ＾ω＾)
⊃パイ⊂

これをこうして…
( ＾ω＾)
≡⊃⊂≡

こうじゃ
( ＾ω＾)
⊃K3s Cluster⊂

![Raspberry pi 4 cluster.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216192/a5cebf0e-880a-ead8-8df5-012ad92fb0e7.jpeg)


## 利用するOS
今回は，[Ubuntu Server 18.04 ARM 64 Bit](https://ubuntu.com/download/raspberry-pi) を利用しています。公式サイトでは既にUbuntu 20.04.1への誘導が始まっています。今回の記事にあたりMasterノードのみUbuntu 20.04.1に上げましたが，日本のAptキャッシュレポジトリがまだfocalに対応していない（アドベントカレンダー執筆時点）ため日本国内から遊びながら利用する場合はまだ18.04の方が良いかと思います。こういう所で書くと対応してくれるんだよなぁ|ω・`）ちら

現在，日本国内に多くのAptミラーレポジトリが存在しますがその全てがARM 64 bitのパッケージを保持してくれているわけでは有りません。私はUbuntu Japanese Teamがメンテナンスしてくれている富山大学さんのミラーを参照しています。Ubuntu ARM 64 bitを利用したことがある方はご存知かと思いますが，AptレポジトリのURL構成がx86/x64とは異なる(ubuntu-ports)ことに注意して下さい。本家のports.ubuntu.comは北米や欧州のサーバに回されることが多いので，Ubuntu Japanese Team並びに富山大学さんには感謝しか有りません。

参考sources.listファイル

```
deb http://jp.archive.ubuntu.com/ubuntu-ports/ bionic main restricted universe multiverse
deb-src http://jp.archive.ubuntu.com/ubuntu-ports/ bionic main restricted universe multiverse

deb http://jp.archive.ubuntu.com/ubuntu-ports bionic-updates main restricted universe multiverse
deb-src http://jp.archive.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe multiverse

deb http://jp.archive.ubuntu.com/ubuntu-ports/ bionic-backports main restricted universe multiverse
deb-src http://jp.archive.ubuntu.com/ubuntu-ports/ bionic-backports main restricted universe multiverse

deb http://jp.archive.ubuntu.com/ubuntu-ports bionic-security main restricted universe multiverse
deb-src http://jp.archive.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse
```

## OSセットアップにおけるTips

### GPUに割り当てるメモリを少なくする
Server用途で利用する際はRaspberry PiでGPUに割り当てるメモリを減らすことで若干メモリを多く利用できます。公式手順は[こちら](https://www.raspberrypi.org/documentation/configuration/config-txt/memory.md)。Ubuntu Serverの場合は/boot/firmware/config.txt と少しファイルの場所が違うことに注意して下さい。

128MB 与える場合

```
# cat /boot/firmware/config.txt  | grep gpu_mem
gpu_mem=128
```

OSが利用できるメモリ量

```
# cat /proc/meminfo|grep Mem
MemTotal:        3831204 kB
MemFree:         2063800 kB
MemAvailable:    3010796 kB
```

これを16MBに減らすと

```
# cat /boot/firmware/config.txt  | grep gpu_mem
gpu_mem=16
```

MemTotalがちょっと増える

```
# cat /proc/meminfo|grep Mem
MemTotal:        3935468 kB
MemFree:          418776 kB
MemAvailable:    3424796 kB
```

### Cgroupを有効にする

K3sのようにコンテナを利用する場合はCgroupの有効化が必要です。検索に引っかかるようにエラーログも載せておきます。

```
Feb 18 14:47:52 kmaster k3s[17960]: time="2020-02-18T14:47:52.543535964Z" level=fatal msg="failed to find memory cgroup, you may need to add \"cgroup_memory=1 cgroup_enable=memory\" to your linux cmdline (/boot/cmdline.txt on a Raspberry Pi)"
```

このような場合は，Cgroupが正しく有効化されていない可能性があります。下記のファイルにcgroup_memory=1 cgroup_enable=memoryを追加して下さい。

```
# cat /boot/firmware/nobtcmd.txt
net.ifnames=0 dwc_otg.lpm_enable=0 console=ttyAMA0,115200 console=tty1 root=LABEL=writable rootfstype=ext4 elevator=deadline rootwait fixrtc cgroup_memory=1 cgroup_enable=memory
```

# K3sをインストールする

K3sのインストールはとても簡単です。ここに手順を書くほどでもなく本家の[インストール手順](https://rancher.com/docs/k3s/latest/en/quick-start/)を参照していただければと思います。

# DNS Cacheコンテナを立てる

IoTやEdgeクラスタにおいてもユースケースが重要です。せっかくクラスタを作っても上に乗せるアプリがないと面白さがないですよね。今回はお家で利用できるDNS CacheサーバをARMコンテナで立ててしまおうというユースケースです。なお，今回立てるDNSコンテナはフルリゾルバでは有りません。

## DNS LookupはTLSで暗号化する

今回のDNS Cacheコンテナは上位のキャッシュDNSサーバへ再帰問い合わせをするのが役割です。CoreDNSはDNS Lookupの暗号化する方式をいくつかサポートしているので，今回はDNS over TLSを用いた例をご紹介します。また今回指定する上位サーバはCloudflareを利用します。

これらを有効化することでDNSのクエリを通信事業者（我々(ﾟ∀ﾟ)）等から秘匿することができます。

[CoreDNS TLS Plugin](https://coredns.io/plugins/tls/)

```
    forward . tls://1.1.1.1 tls://1.0.0.1 {
           tls_servername cloudflare-dns.com
           health_check 5s
           policy round_robin
    }
```

## 広告を配信しているドメインをフィルターする

普段生活していると危険なサイトや不要な広告は予めブロックして欲しいと感じることがよくあります。今回は[悪いインターネット](https://warui.intaa.net/adhosts/)さん（名前は悪そうですが個人的には好きです）が提供している広告除去用ホストファイルというのをCoreDNSにインテグレーションしてその実現をしてみます。

下記のようにローカルに存在するHostsファイルをDNS解決に利用するようにします。HostsファイルはCoreDNSのInitContainerで上記のサイトよりダウンロードしてきてプロビジョニングします。

[CoreDNS Hosts Plugin](https://coredns.io/plugins/hosts/)

```
    hosts /etc/adblock-host/hosts {
           fallthrough 
    }
```

※上記のHostsファイルの利用は飽くまで自己責任でお願い致します

## KubernetesのYamlにする

CoreDNSの設定はファイルはConfigmapとして与えます。ヘルスチェック等のPluginも有効化しておいた方が実践的なので有効化してあります。あとはCoreDNSのDeploymentにInitContainerを設定してHostsファイルを起動時に取得するようにします。よってPodが起動するたびに最新のHostsファイルを取得します。そして最後にServiceを作ります。今回はClusterIPで作成し，ゲートウェイを兼ねているルータでK3sクラスタへルーティングを書いています。もちろんK3sが提供するコントローラーを用いてType LoadBalancerで作成してもOKです。

```
apiVersion: apps/v1
kind: Deployment
metadata:
  name: coredns
  labels:
    k3s-app: dns-server
spec:
  replicas: 1
  selector:
    matchLabels:
      k3s-app: dns-server
  template:
    metadata:
      labels:
        k3s-app: dns-server
    spec:
      containers:
      - name: sekinet-dns
        image: coredns/coredns:1.8.0
        args:
        - -conf
        - /etc/coredns/Corefile
        imagePullPolicy: IfNotPresent
        livenessProbe:
          failureThreshold: 5
          httpGet:
            path: /health
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 60
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 5
        name: coredns
        ports:
        - containerPort: 53
          name: dns
          protocol: UDP
        - containerPort: 53
          name: dns-tcp
          protocol: TCP
        readinessProbe:
          failureThreshold: 5
          httpGet:
            path: /ready
            port: 8181
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 5
        resources:
          limits:
            memory: 170Mi
          requests:
            cpu: 100m
            memory: 70Mi
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            add:
            - NET_BIND_SERVICE
            drop:
            - all
          readOnlyRootFilesystem: true
        volumeMounts:
        - mountPath: /etc/coredns
          name: config-volume
          readOnly: true
        - name: adblock-host
          mountPath: /etc/adblock-host
          readOnly: true
      initContainers:
      - name: download-adblock-hosts
        image: lucashalbert/curl
        args:
        - -v
        - https://warui.intaa.net/adhosts/hosts.txt
        - --output
        - /etc/adblock-host/hosts
        volumeMounts:
        - name: adblock-host
          mountPath: /etc/adblock-host
      volumes:
      - configMap:
          defaultMode: 420
          items:
          - key: Corefile
            path: Corefile
          name: coredns
        name: config-volume
      - name: adblock-host
        emptyDir: {}
---
apiVersion: v1
data:
  Corefile: |
    .:53 {
        hosts /etc/adblock-host/hosts {
           fallthrough
        }
        forward . tls://1.1.1.1 tls://1.0.0.1 {
           tls_servername cloudflare-dns.com
           health_check 5s
           policy round_robin
        }
        cache 300
        log
        errors
        health
        ready
        reload
    }
kind: ConfigMap
metadata:
  name: coredns
---
apiVersion: v1
kind: Service
metadata:
  labels:
  name: dns-server
spec:
  clusterIP: 10.43.0.53
  type: ClusterIP
  selector:
    k3s-app: dns-server
  ports:
  - name: dns
    port: 53
    protocol: UDP
  - name: dns-tcp
    port: 53
    protocol: TCP
  sessionAffinity: None
```

## 動作確認

こんな感じでCoreDNSがログを出力します

```
# kubectl -n yournamespace logs coredns-56fd7bd897-l642s
[INFO] 10.42.1.1:3133 - 41029 "AAAA IN qiita.com. udp 27 false 512" NOERROR qr,rd,ra 138 0.040631835s
[INFO] 10.42.1.1:44694 - 60473 "A IN qiita.com. udp 27 false 512" NOERROR qr,rd,ra 102 0.045602764s
[INFO] 10.42.1.1:38573 - 46515 "AAAA IN ssl.gstatic.com. udp 33 false 512" NOERROR qr,aa,rd,ra 76 0.00043194s
[INFO] 10.42.1.1:61158 - 47323 "A IN ssl.gstatic.com. udp 33 false 512" NOERROR qr,rd,ra 64 0.02043012s
[INFO] 10.42.1.1:8413 - 10128 "A IN clients6.google.com. udp 37 false 512" NOERROR qr,rd,ra 126 0.006006271s
[INFO] 10.42.1.1:57616 - 9111 "AAAA IN clients6.google.com. udp 37 false 512" NOERROR qr,rd,ra 138 0.020215048s
```

もちろんパケットキャプチャしても再帰問い合わせのクエリはTLSで保護されて中身を見ることは出来ません。
![Screen Shot 2020-12-07 at 1.56.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216192/4260f4e9-555f-0049-2a02-eb86bbdd97b8.png)


# まとめ

今回は，お家Kubernetesのユースケースの1つとして，Raspberry Pi 4, Ubuntu 18.04 ARM 64-bit, K3s, CoreDNS等を組み合わせて実用できるお家DNS Cacheコンテナをご紹介しました。最近はパブリッククラウドサービスを利用する機会が多く，自分で意識的にやらないとEdgeやIoTのユースケースを触る機会が少なくなってしまったなぁと思います。しかし，OSの管理からKubernetesのプラットフォーム，その上に乗るアプリまで常に新しいものに触れる機会はとても大事なのでこの記事を読んでくれた方々もパブリッククラウドなら5分で出来ることを何時間もかけてやってみませんか？(*^^*)

明日は @khrd さんの[KindでVirtualClusterを試そう](https://qiita.com/khrd/items/f1fb4360ebe4aabe72ca)です！お楽しみに！！！

追伸: 水曜どうでしょうの新作が楽しみです♪

## お家DNSレコードを見ていて面白かったこと

- 自分の活動量がDNSクエリ量に比例するので可視化すると面白そう
- クエリレコードの殆どがAやAAAAレコードなのですが，最近[HTTPSレコード](https://datatracker.ietf.org/doc/draft-ietf-dnsop-svcb-https/)がチラホラ出るようになりました。Apple, Google, Akamai等が先んじて提供していることが観測できました。

## 参考文献

- [K3s - Rancher](https://rancher.com/docs/k3s/latest/en/)
- [Install Ubuntu on a Raspberry Pi 2, 3 or 4](https://ubuntu.com/download/raspberry-pi)
- [Ubuntuのパッケージアーカイブミラー一覧](https://www.ubuntulinux.jp/products/JapaneseMirrors)
- [DNS over TLS/HTTPSについて考える](https://eng-blog.iij.ad.jp/archives/2954)
- [CoreDNS](https://coredns.io/)
- [Service binding and parameter specification via the DNS (DNS SVCB and HTTPS RRs)
](https://datatracker.ietf.org/doc/draft-ietf-dnsop-svcb-https/)