メモ - RyuSA

技術的なメモ書きとか試してみたこと

kubenews#57 Cloud Nativeなニュース - プロダクト紹介縛り

本記事はkubenewsの第57回目のゲスト枠で参加するRyuSAのコンテンツです。

なお、本登壇および本記事はあくまで 私自身の見解 であり、私の所属団体・企業における立場、戦略、意見を代表するものではありません。

Markdoc

markdoc.io

Stripe社の公開したドキュメントをMarkdownで書くためのOSSです。利用できるMarkdownGitHub Flavored……でありつつも一部AsciiDoc的に似た拡張を使ってドキュメントを記述できます。そのため多くのエンジニアが普段から書き慣れているMarkdownを使ってドキュメントのメンテナンスを行うことができます。

---
title: What is Markdoc?
---

# {% $markdoc.frontmatter.title %} {% #overview %}

Markdoc is a Markdown-based syntax and toolchain for creating custom documentation sites. Stripe created Markdoc to power [our public docs](http://stripe.com/docs).

{% callout type="check" %}
Markdoc is open-source—check out its [source](http://github.com/markdoc/markdoc) to see how it works.
{% /callout %}

## How is Markdoc different?

Markdoc uses a fully declarative approach to composition and flow control, where other solutions… [Read more](/docs/overview).

## Next steps
- [Install Markdoc](/docs/getting-started)
- [Explore the syntax](/docs/syntax)

またNext.jsやReactなどと組み合わせて複雑なビルドやレイアウトのごにょごにょができたりと、拡張の方法はかなり自由のため社内WikiのGit管理などに使えるような気がします。

Talos Linux

www.talos.dev

Kubernetes"専用"のOS……え??専用???となりますがそんな不思議なOSがTalos Linux。このLinuxディストリビューションはコンテナ向けOSLinuxのひとつで、特にKubernetesを起動/運用するためのものです。OSにSSHすることはできず、設定はすべてはtalosctlを使ったAPI経由で行うことになります。

Talos LinuxはDockerで起動することが可能で、手元でサクッと動作検証することができます。

個人的に好みなのは、このOS自体がメモリ上で展開できるためサーバーがステートフルになることを防ぐことができます。PXEブートなどにも当然対応しているため、Raspberry Piなどが活躍するワークロード(IoTや誤自宅勢とか)にはとても便利なんじゃないかなと思います。

trivy

超便利だったので共有したい、というだけ……

aquasecurity.github.io

23:22:35 ryusa@TUNA-KAN:~/develop/local/kubenews#57
$ trivy kubernetes --report summary cluster
2022-07-13T23:22:54.708+0900    INFO    Need to update DB
2022-07-13T23:22:54.708+0900    INFO    DB Repository: ghcr.io/aquasecurity/trivy-db
2022-07-13T23:22:54.708+0900    INFO    Downloading DB...
32.96 MiB / 32.96 MiB [---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------] 100.00% 19.91 MiB p/s 1.9s
141 / 141 [----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------] 100.00% 7 p/s

Summary Report for admin@talos-default
┌─────────────┬──────────────────────────────────────────────────────────────┬────────────────────┬────────────────────┬───────────────────┐
│  Namespace  │                           Resource                           │  Vulnerabilities   │ Misconfigurations  │      Secrets      │
│             │                                                              ├───┬───┬───┬────┬───┼───┬───┬───┬────┬───┼───┬───┬───┬───┬───┤
│             │                                                              │ C │ H │ M │ L  │ U │ C │ H │ M │ L  │ U │ C │ H │ M │ L │ U │
├─────────────┼──────────────────────────────────────────────────────────────┼───┼───┼───┼────┼───┼───┼───┼───┼────┼───┼───┼───┼───┼───┼───┤
│ kube-system │ Role/system::leader-locking-kube-scheduler                   │   │   │   │    │   │   │   │ 1 │    │   │   │   │   │   │   │
│ kube-system │ Role/system::leader-locking-kube-controller-manager          │   │   │   │    │   │   │   │ 1 │    │   │   │   │   │   │   │
│ kube-system │ Deployment/coredns                                           │ 1 │ 1 │   │    │   │   │   │ 3 │ 5  │   │   │   │   │   │   │
│ kube-system │ DaemonSet/kube-flannel                                       │   │ 6 │ 8 │ 6  │ 2 │   │ 2 │ 9 │ 30 │   │   │   │   │   │   │
│ kube-system │ Role/system:controller:cloud-provider                        │   │   │   │    │   │   │   │ 1 │    │   │   │   │   │   │   │
│ kube-system │ Role/system:controller:token-cleaner                         │   │   │   │    │   │ 1 │   │   │    │   │   │   │   │   │   │
│ kube-system │ Role/system:controller:bootstrap-signer                      │   │   │   │    │   │ 1 │   │   │    │   │   │   │   │   │   │
│ kube-system │ DaemonSet/kube-proxy                                         │ 6 │ 8 │ 2 │ 57 │   │   │ 2 │ 4 │ 10 │   │   │   │   │   │   │
│ kube-system │ Service/kube-dns                                             │   │   │   │    │   │   │   │ 1 │    │   │   │   │   │   │   │
│ kube-public │ Role/system:controller:bootstrap-signer                      │   │   │   │    │   │   │   │ 1 │    │   │   │   │   │   │   │
│             │ ClusterRole/cluster-admin                                    │   │   │   │    │   │ 2 │   │   │    │   │   │   │   │   │   │
│             │ ClusterRole/system:controller:namespace-controller           │   │   │   │    │   │ 1 │   │   │    │   │   │   │   │   │   │
│             │ ClusterRole/system:controller:job-controller                 │   │   │   │    │   │   │ 1 │   │    │   │   │   │   │   │   │
│             │ ClusterRole/system:controller:endpointslice-controller       │   │   │   │    │   │   │ 1 │   │    │   │   │   │   │   │   │
│             │ ClusterRole/system:controller:root-ca-cert-publisher         │   │   │   │    │   │   │   │ 1 │    │   │   │   │   │   │   │
│             │ ClusterRole/system:kube-scheduler                            │   │   │   │    │   │   │ 2 │   │    │   │   │   │   │   │   │
│             │ ClusterRole/admin                                            │   │   │   │    │   │ 3 │ 7 │ 1 │    │   │   │   │   │   │   │
│             │ ClusterRole/system:controller:endpoint-controller            │   │   │   │    │   │   │ 1 │   │    │   │   │   │   │   │   │
│             │ ClusterRole/system:controller:resourcequota-controller       │   │   │   │    │   │ 1 │   │   │    │   │   │   │   │   │   │
│             │ ClusterRole/system:controller:replicaset-controller          │   │   │   │    │   │   │ 1 │   │    │   │   │   │   │   │   │
│             │ ClusterRole/system:controller:generic-garbage-collector      │   │   │   │    │   │ 1 │   │   │    │   │   │   │   │   │   │
│             │ ClusterRole/edit                                             │   │   │   │    │   │ 2 │ 7 │ 1 │    │   │   │   │   │   │   │
│             │ ClusterRole/system:aggregate-to-edit                         │   │   │   │    │   │ 2 │ 7 │ 1 │    │   │   │   │   │   │   │
│             │ ClusterRole/system:controller:deployment-controller          │   │   │   │    │   │   │ 2 │   │    │   │   │   │   │   │   │
│             │ ClusterRole/system:controller:horizontal-pod-autoscaler      │   │   │   │    │   │ 2 │   │   │    │   │   │   │   │   │   │
│             │ ClusterRole/system:controller:persistent-volume-binder       │   │   │   │    │   │ 1 │ 2 │   │    │   │   │   │   │   │   │
│             │ ClusterRole/system:controller:expand-controller              │   │   │   │    │   │ 1 │   │   │    │   │   │   │   │   │   │
│             │ ClusterRole/system:controller:replication-controller         │   │   │   │    │   │   │ 1 │   │    │   │   │   │   │   │   │
│             │ ClusterRole/system:aggregate-to-admin                        │   │   │   │    │   │ 1 │   │   │    │   │   │   │   │   │   │
│             │ ClusterRole/system:controller:endpointslicemirroring-contro- │   │   │   │    │   │   │ 1 │   │    │   │   │   │   │   │   │
│             │ ller                                                         │   │   │   │    │   │   │   │   │    │   │   │   │   │   │   │
│             │ ClusterRole/system:kube-controller-manager                   │   │   │   │    │   │ 5 │ 2 │   │    │   │   │   │   │   │   │
│             │ ClusterRole/system:node                                      │   │   │   │    │   │ 1 │   │   │    │   │   │   │   │   │   │
│             │ ClusterRole/system:controller:cronjob-controller             │   │   │   │    │   │   │ 2 │   │    │   │   │   │   │   │   │
└─────────────┴──────────────────────────────────────────────────────────────┴───┴───┴───┴────┴───┴───┴───┴───┴────┴───┴───┴───┴───┴───┴───┘
Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN

う、美しい……!(jsonでのレポートもできるので自動化もしやすいたすかる)

Trivy Operator

github.com

というそんなTrivyのOperatorについても紹介、要するに↑をKubernetes内部からチェックしてくれるとても素晴らしいOperator。CRとしてPodや各種設定の脆弱性を出力してくれます。

00:14:21 ryusa@TUNA-KAN:~/develop/local/kubenews#57
$ kubectl apply -f https://raw.githubusercontent.com/aquasecurity/trivy-operator/v0.1.3/deploy/static/trivy-operator.yaml
customresourcedefinition.apiextensions.k8s.io/vulnerabilityreports.aquasecurity.github.io created
customresourcedefinition.apiextensions.k8s.io/configauditreports.aquasecurity.github.io created
customresourcedefinition.apiextensions.k8s.io/exposedsecretreports.aquasecurity.github.io created
customresourcedefinition.apiextensions.k8s.io/clusterconfigauditreports.aquasecurity.github.io created
customresourcedefinition.apiextensions.k8s.io/clusterrbacassessmentreports.aquasecurity.github.io created
customresourcedefinition.apiextensions.k8s.io/rbacassessmentreports.aquasecurity.github.io created
namespace/trivy-system created
serviceaccount/trivy-operator created
clusterrole.rbac.authorization.k8s.io/trivy-operator created
clusterrole.rbac.authorization.k8s.io/aggregate-config-audit-reports-view created
clusterrole.rbac.authorization.k8s.io/aggregate-exposed-secret-reports-view created
clusterrole.rbac.authorization.k8s.io/aggregate-vulnerability-reports-view created
clusterrolebinding.rbac.authorization.k8s.io/trivy-operator created
role.rbac.authorization.k8s.io/trivy-operator created
rolebinding.rbac.authorization.k8s.io/trivy-operator created
role.rbac.authorization.k8s.io/trivy-operator-leader-election created
rolebinding.rbac.authorization.k8s.io/trivy-operator-leader-election created
secret/trivy-operator created
secret/trivy-operator-trivy-config created
configmap/trivy-operator created
configmap/trivy-operator-trivy-config created
configmap/trivy-operator-policies-config created
Warning: would violate PodSecurity "restricted:latest": runAsNonRoot != true (pod or container "trivy-operator" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "trivy-operator" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")
deployment.apps/trivy-operator created
service/trivy-operator created

00:21:19 ryusa@TUNA-KAN:~/develop/local/kubenews#57
$ kubectl create deployment nginx --image nginx:1.16
Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "nginx" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "nginx" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "nginx" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "nginx" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")
deployment.apps/nginx created

00:22:10 ryusa@TUNA-KAN:~/develop/local/kubenews#57
$ kubectl get vulnerabilityreports -o wide
NAME                                REPOSITORY      TAG    SCANNER   AGE   CRITICAL   HIGH   MEDIUM   LOW   UNKNOWN
replicaset-nginx-7f597d689d-nginx   library/nginx   1.16   Trivy     32s   38         74     57       119   1

現在の最新バージョンで用意されているのは - clusterconfigauditreports - clusterrbacassessmentreports - configauditreports - exposedsecretreports - rbacassessmentreports - vulnerabilityreports の6つのリソース、雑に環境に放り込んでおくだけでそれっぽくなりそうな予感

Sigstore

www.sigstore.dev

ソフトウェアのサプライチェーンを防ぐための仕組みを考えよう!という実験的なプロジェクト Sigstoreは - cosign - Fulcio - Rekor の3つのソフトウェアを組み合わせて機能でき、最終的な目的としては「アーティファクトをキーレスで署名して、その署名を改竄できないログとして保存して、アーティファクトを公開する」ことです。これにより、だれでもアーティファクトが正しく署名されていることを検証することができつつ鍵の管理が不要なので色々幸せになれそうな予感……!

残念ながら手元のWindows環境ではなぜかうまく動かなかった&話するとかなり長くなるので詳細は先人の神資料を参考に……

knqyf263.hatenablog.com

OCIイメージに対しての署名もできる一方、当然普通にバイナリにも署名ができるのでJavaのGradleやRubyRubyGemsなどでIssueとしてあがっています。Sigstoreが広がって、すべてのソフトウェアのサプライチェーンを守れるようなそんな世界が来るといいですね。

github.com

github.com

とあるYouTube Streamerのファンサイトを作った話

この活動は自分個人の趣味であり、所属する企業や組織の立場、戦略、意見を代表するものではありません。

こんなの作ってました。たのしかった(小並感

milliooon.ryusa.app

リリースしてからしばらく経ったので、少し技術的な選定の話とかをまとめておきます。尖ったことはあまりしてないのでアレですが……🤔

Question. そもそもなんで作ったの?

Answer. 久しぶりにフロントエンド作りたかったから🤪

ここ最近はKubernetesDNSなどのインフラストラクチャーばかり触ってたので、たまにはリフレッシュしたいな〜と……

要件と技術選定を進める

今回の要件はこんな感じでした。個人プロジェクトなので自分中心で楽しくやりたいってのが理由ですね。

  • まだ触ったことがないものに挑戦したい
    • 個人開発である以上モチベを維持しやすいように新しいものに触れることを前提に
  • 安く早く済ませたい
    • 言わずもがな
  • マネージドなサーバーを持ちたくない
    • 管理したくない(怠惰
    • ホットなコンテンツを相手にする以上、もしかしたらスパイクが発生するかも……?(という想定訓練は大事)
  • デプロイ自動化が可能
    • 令和ですよ?Continuous Deploymentくらいは標準搭載ですよね??
    • 万が一怒られた際にロールバックがスムーズにできるようにする(これから怒られる可能性はまだありますので、怒られたら速攻で消します)
  • ガイドラインを読む(超大事)
  • ガイドラインを読む(大事なことなので(ry
  • ガイドラインを読む(超大事なことなので(ry

ということを前提に技術選定を進めました。

プラットフォーム

まずはプラットフォームの選定。今回のコンテンツ的には静的なもので十分(=バックエンドで動的にコンテンツを生成する必要がない)のでサーバーどころかLambdaすら不要。単純なSPA等の静的ファイル配信ができればOKです。

そこで今回はNetlifyかVercelかCloudflare Pagesのいずれかをプラットフォームとして使おうかなと🤔Netlify / Vercel / Cloudflare Pages はいずれもフロントエンドホスティングサービスで、ReactやVueなどで作ったSPAなどをホスティングCDN配信してくれるとても素敵なサービスたちです。……が、実はNetlifyはかつて使ったことがあり、いろんなところで話題に聞くVercelが気になったので今回はまったく触ったことがないVercelでホストすることに決めました👀ごめんねCloudflare Pages

ドメイン名とか証明書とか

このファンサイトは二次創作のプロダクトであり、万が一ご本家様とドメイン名争奪戦が起こっては大変なので自分のドメインryusa.appからのサブドメインを切り出しました。

Vercelは、ホスティングサイトなので当然ではありますがカスタムドメインを利用してサイトを公開することができます。

vercel.com

自分のryusa.appGoogle Domainsで用意しているドメインなので、GoogleDNSにVercel向けのCNAMEだったりTXTレコードだったりを登録してVercel側にドメインの登録を完了しました。超簡単。

……ちなみにVercelにドメインの登録をすると、自動的にLet’s EncryptTLS証明書を発行してくれるようになります。マジでなにも考えなくてもTLS証明書がホストされるようになります。超簡単。

vercel.com

言語とか

今回はTypeScript/Reactベースで実装することを決定しました。最初は今アツいSvelteで行こうかと思いましたが、React 18がちょうど出たタイミングだったので波に乗るなら今だ!!と思って今回はReactベースにしました。Svelteはまた今度ね……🥰また最初はSPAとしてビルドしようと思っていましたが、フロントエンドの規模的にそこまで大きくなく、かつ動的なAPI呼び出しも発生しないことを踏まえると静的ビルドで十分と判断し、今回はまだ触ったことのないGatsbyを利用することにしました。

ちなみに、実装中に検証としてExperimentalで公開されてるReactのServer Componentも触ってみましたがかなり良さげでした。そこまでコストかけずにバンドルサイズを減らせるのは強い……!ただ今回は検証の時間がそこまでなかったので見送り……

CI/CD

GitHub ActionでビルドしてVercel側で自動デプロイ……というのが当初の予定でしたが、実はそもそもVercel側にGitリポジトリをfetchして自動ビルドをかける機能が存在しているんですよね。自分は知らなかったのですが、Vercelにはリポジトリを連携するとリポジトリの中身をチェックして自動的にビルドコマンドとデプロイコマンドを生成してくれる神機能が付いていました。

ビルドコマンドや環境設定をカスタマイズすることもできる

実装周りとか

そんな難しいことしてないし特に書くものなくね??と思っていましたが、個人的に気を配ったことだけ……

IEのサポートを、しない

力強く!

日本はやたらIEシェアが高いらしいですが、別にお仕事でもないプライベートプロジェクトなのでIE対応はしませんでした。React 18からIEのサポートも切れることですしIEそのものも消えますし、まぁ妥当かなと。

ja.reactjs.org

(IEを除く)ブラウザシェア上位 95% を(目安に)サポートする

ファンサイトとしてホストする以上、モダンブラウザをサポートしておかないと場合によっては他の熱心なファンが応援できない可能性があります。「(レイアウト崩れを見て)なんだよーFirefoxはサポートしてくれないのかー」となっては悲しいですし、せっかくの盛り上がりに水を差してしまうでしょう。ということで今回はChromeを始めとしてブラウザシェア95%に入るブラウザをサポートすることを目標に動作検証を行いました。なのでCSSsvh@layerなどの超モダンな機能群が使えなくて少し悲しいところではありましたが……

業務ではモダンブラウザサポートなんて当然の作業ですが、個人プロジェクトで動作検証しっかりやったのは初めてでしたね……一部環境の動作検証にお手伝いいただいた方々には、改めてこの場で感謝を。ありがとうございました!(iPhone民/Samsung民)

まとめ

Vercelとフロントエンド周りのエコシステムが本当に充実しており、CI/CDの環境をサクッと構築できるおかげで手元で開発して本番デプロイまでが数分でできる世界観でした。ブランチで本番環境にデプロイするアセットを管理できるので、これであれば大規模なフロントエンド開発チームでの開発フローにも導入できそうです。

今度Cloudflare Pagesも使ってみる予定なので、もしタイミングがあれば具体的なNetlify / Vercel / Cloudflare Pages の比較ができるかもしれないですね👍


最後に、お手伝いいただけたみなさんありがとうございました。wappaさんも素敵なイラストを提供&OGP画像作成ありがとうございました。

twitter.com

久しぶりの人目に触れるプロダクトを公開し、いろんな人からの感想がもらえたのはすごく励みになりました👏ありがとうございました!

あいあいじぇいを退職します👋

エンジニアもすなる退職ブログといふものを……

2022年3月31日を最終出社日として、株式会社インターネットイニシアティブを退職します👏 新卒で入社してちょうど4年間、大変お世話になりました。

退職ブログを書いてみようと筆を走らせてみたものの、思いのほかたくさん書けそうになく……ですが、せっかくなので少しだけですが、将来の自分のためにもエントリーを残しておきます

IIJでどんなことしていたの

僕はIIJのとある金融サービスの部署のエンジニアとして仕事をしていました。最終的な肩書きがただの「エンジニア」なのは、それはそれでちょっぴり寂しい気もする……ww

主な業務内容としては次のようなものに携わってきました。

  • アプリケーション開発
    • フロントエンド
    • バックエンド
  • インフラ開発
    • AWS関連
    • Kubernetes
    • プラットフォームエンジニア的なやつ

……おぉ、こうやって並べてみると専門領域がフワッフワしてるなぁw スキルセット的にはインフラ領域1に対してアプリケーション3……ぐらいの立ち位置ですね🤔

最初はアプリケーションエンジニアとして金融システムを開発し、その後インフラに近い領域からプラットフォームエンジニア的な立ち位置で開発メンバーの補佐や技術検証などをやってきました。とはいえ今でも自分の一番の専門と言える領域はアプリケーション開発ですね。コーディング楽しい🤪

メイン業務とは若干外れますが、IIJのブログにこんなネタを投稿していました。多々反響もらえて(特にKubernetesネタ)非常に楽しかったですw

eng-blog.iij.ad.jp

eng-blog.iij.ad.jp

Why 退職?

退職する主な理由は、転職するからです。(?🤔?)

新しい環境で挑戦してみたいな〜と考えていた矢先、とあるクラウドベンダーさんとお仕事する機会があったので挑戦してみようと思い転職を決めました。やっぱりね、刺激が欲しいんですよww

おそらく今の環境に残っていても様々なキャリア、技術面を磨くことはできるとは思いますが……それはあくまでもIIJという会社から見える世界でのみ、やっぱり一度外に出てみたいなと思うわけです。

最後に

4年間、面倒を見てくださったメンバーに最大限の感謝を。たくさん尻拭いしてくださり本当に感謝しております。この4年間は、自分にとって大切な4年間になりました👏

また社内に転がっている様々なコミュニティ(IIJ Bootcamp GitHub - iij/bootcamp: Bootcamp ハンズオンで使用する資料集 とか)での会話も非常に楽しかったです。気軽に困ったことや技術ネタをネットミームとともに盛り上がれる場があるのは、なんというかすごくIIJらしさを感じてますw

とはいえ退職するからと言って人が変わるわけでもなく、今後もTwitter等で元気よく自生しておりますので

ではでは👋

技術書典 #12で「Java開発 for Kubernetes」を頒布します🎉

はじめての技術同人誌ですが、がんばります💪

Java開発 for Kubernetes:ツナ缶本舗 | 技術書典12

どんな書籍?

「次のプロジェクトでは、DockerとKubernetesクラウドネイティブだ!」 ……なーんて話を聞かされて、困っている開発者の人はいませんか?

アプリケーションの実行環境をコンパクトにまとめた隔離環境「コンテナ」 コンテナを自在にデプロイし面倒くさい管理を自動化できるエコシステム「Kubernetes

大コンテナ時代を迎えようとしている今、どのようにJavaでアプリケーションを書き、どのように開発フローを回し、どのように運用/監視していくのか、ざっくり表面だけ舐め回すことができるコンテンツを集めました。 JavaのWebアプリケーションとしてSpring Bootを使いながら、実際にコマンドを叩いてアプリケーションをコンテナにしてKubernetesにデプロイする開発フローを体験してみましょう!

という感じの本です。つまるところ「Kubernetes上で動かすコンテナアプリケーションを開発するにあたって、どんなツールがあったり嬉しいものがあったりするの?」ってのをざっくりをまとめたエッセンス本です。注意していただきたいのは「これを読めばKubernetes完全に理解できる!」タイプの本ではないです。マジでアプリケーションサイド目線の話しか書いていません。なのでインフラの話はほぼ出てきません。

コンテンツ

  • 含まれているもの
    • コンテナの概要説明
    • Kubernetesの概要説明
    • コンテナのための開発フロー体験
    • 開発ヘルパーツールの紹介とざっくり使い方
    • 運用のために覚えておくこと
    • これらのサンプルコード
  • 含まれていないこと
    • Kubernetesのバックエンド(kubeadmとかkubeletとか)
    • インフラの話(OSとかCNIとかコンテナランタイムとか)

ターゲットとなる人

主に開発者、特にJava/Spring Bootの開発を行なっている人にとっては良い本だと思っています。またJavaで書かれたワークロードをメンテナンスするぜ!という(広義での)SREの方も読んでおくとアプリケーション開発者と会話しやすいかもしれないです。 自分はアプリケーション開発者である一方でCKA等も取得しKubernetes完全にわからんを通過してきた身でもあります。その背景を元に「こんな情報が横断的にあったら嬉しかったのにな〜」というものをかき集めてました。すいません、実は書ききれてないです物理的な予算が足りなかったです。

ということで、買って💕

電子書籍版は1000円、電子書籍+物理本セットも1000円で頒布しています!一応別の場所でも頒布する予定はありますが、お値段上がったり電子版/物理本いずれか……など、セットでは購入できなくなると思います。

GitLab OperatorはGitLab運用の自動化の夢を見るか

注意 : GitLab Operatorは10月9日現在まだv0.1.0のリリースがあったばかりです。本番環境への適用にはまーだ早いのでご注意してください🤔

–– さぁ、GitLab OperatorでGitLab運用の自動化をはじめよう!!

gitlab.com

背景

GitLabはおそらく多くのエンジニア、組織で利用されているGitリポジトリ管理ツールです。しかし、GitLabには多様な機能が日々追加されており今やGitLabはただのGitリポジトリツールではなくっている……と個人的に思っています。

GitLabのIssue/Milestoneをはじめ、MavenやコンテナイメージのリポジトリKubernetes Integrationなど多様なコミュニケーション機能や開発者向けの機能が追加されています。GitLabに求められる可用性は日々上がっている一方、ますます複雑になるGitLab運用は人間の手でやりたくないですよね。

そこで使えるのがGitlab Operator、Helmに次ぐ新しい"Cloud Native"なGitlabホスト方法です。

全体像

GitLab Operatorは様々なコンポーネントと接続してGitLabを起動する

検証環境

AWSのEKS上で実施します。ただEKS専用の機能を使う部分は限定的なため、他のKubernetes環境でも同じように起動することができるはずです。

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: ryusa-gitlab
  version: "1.21"

# 後述のExternalDNSとcert-managerをRoute53で管理するためのIRSAを定義
iam:
  withOIDC: true
  serviceAccounts:
  - metadata:
      name: external-dns
      namespace: external-dns
      labels: {}
    wellKnownPolicies:
      externalDNS: true
    roleName: eksctl-ryusa-external-dns-role
  - metadata:
      name: cert-manager
      namespace: cert-manager
      labels: {}
    wellKnownPolicies:
      certManager: true
    roleName: eksctl-ryusa-cert-manager

managedNodeGroups:
  - name: workers
    minSize: 1
    maxSize: 6
    desiredCapacity: 3
    spot: true
    instanceTypes: 
          - t3.medium
          - t3.large
    volumeSize: 60
    volumeType: "gp3"

Getting Startedする

依存関係のセットアップ

GitLab Operatorは

  • NGINX Ingress Controller
  • cert-manager
  • DNSを変更できるツール(今回はExternalDNSを採用)

を利用してGitLabを起動します。まずはこれらを適当にKubernetesにデプロイしておきましょう。

NGINX Ingress Controller

GitLabをKubernetesの外からアクセスできるようにするためのIngress ControllerとしてNGINX Ingress Controllerが推奨されています。実際にはIngress Controllerとして

  • Ingressのプロビジョニングができる
  • TCP:22でProxyができる(SSH用)

の2点が必要な機能だと思います。

kubernetes.github.io

推奨に従ってNGINX Ingress ControllerをKubernetesにHelmでデプロイします。使うHelmチャートはingress-nginx/ingress-nginxValueファイルは下記のYAMLで実装します。

# Helmチャート ingress-nginx/ingress-nginx 

controller:
  ingressClassResource:
    name: nginx
    enabled: true
    default: true
 
# NGINX Ingress ControllerにSSH用のTCPプロキシーを作成する
# 22: ${GITLAB_NAMESPACE}/${GITLAB_NAME}-gitlab-shell:22
tcp:
  22: "gitlab-system/vulture-gitlab-shell:22"

cert-manager

GitLabのTLS証明書を発行するためにインストールが必須です、GitLabのシステム内部で利用する自己証明書の作成もcert-managerのIssuerを使って実装されています。

cert-manager.io

推奨に従ってcert-managerをKubernetesにHelmでデプロイします。使うHelmチャートはjetstack/cert-managerValueファイルは下記のYAMLで実装します。一部項目にEKSからRoute53を変更するためのIRSAの設定が入ってますが、不要な方はスルーしてください。

# Helmチャート jetstack/cert-manager

installCRDs: true
 
# EKSのIRSAを利用するための設定
serviceAccount:
  create: false
  name: cert-manager
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::XXX:role/eksctl-ryusa-cert-manager
 
securityContext:
  enabled: true
  fsGroup: 1001

今回はIssuerとしてLet's Encryptを利用するため、Let's EncryptのACMEを持つClusterIssuerを先に作成しておきます。

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt
  namespace: cert-manager
spec:
  acme:
    email: YOUR_EMAIL
    privateKeySecretRef:
      name: letsencrypt-privatekey
    server: https://acme-v02.api.letsencrypt.org/directory
    solvers:
    - dns01:
        route53:
          region: YOUR_REGION
          hostedZoneID: HOST_ZONE_ID

External DNS

Ingressに割り振るドメイン名を自動的にDNSに設定するツールのインストールが推奨されています。GitLabをデプロイ後、デフォルトの状態で「GitLab」「MinIO」「Repository」の3つのIngressが作成され、それぞれ適切なドメイン名で名前解決できることが期待された状態でコンポーネントが起動してきます。

github.com

もちろん、Ingress作成後に手動でDNSを操作しても良いですが……素直にExternalDNSを利用した方が良いでしょう。今回はExternalDNSの公式リポジトリでホストされているマニフェストを利用してデプロイすることにします。

# 公式のRBACの定義は省略
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: external-dns
  namespace: external-dns
spec:
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: external-dns
  template:
    metadata:
      labels:
        app: external-dns
      annotations:
        # IRSAのための設定
        iam.amazonaws.com/role: arn:aws:iam::XXX:role/eksctl-ryusa-external-dns-role
    spec:
      serviceAccountName: external-dns
      containers:
      - name: external-dns
        image: k8s.gcr.io/external-dns/external-dns:v0.7.6
        args:
        - --source=service
        - --source=ingress
        - --domain-filter=ryusa.example.com
        - --provider=aws
        - --policy=upsert-only
        - --aws-zone-type=public
        - --registry=txt
        - --txt-owner-id=some-identifier
      securityContext:
        fsGroup: 65534

GitLab Operatorインストール

公式ドキュメントに沿ってインストールしていきます。

docs.gitlab.com

❯ GL_OPERATOR_VERSION=0.1.0
❯ PLATFORM=kubernetes                
❯ kubectl create namespace gitlab-system
namespace/gitlab-system created
❯ kubectl apply -f https://gitlab.com/api/v4/projects/18899486/packages/generic/gitlab-operator/${GL_OPERATOR_VERSION}/gitlab-operator-${PLATFORM}-${GL_OPERATOR_VERSION}.yaml
Warning: apiextensions.k8s.io/v1beta1 CustomResourceDefinition is deprecated in v1.16+, unavailable in v1.22+; use apiextensions.k8s.io/v1 CustomResourceDefinition
customresourcedefinition.apiextensions.k8s.io/gitlabs.apps.gitlab.com created
serviceaccount/gitlab-app created
serviceaccount/gitlab-manager created
serviceaccount/gitlab-nginx-ingress created
role.rbac.authorization.k8s.io/gitlab-leader-election-role created
role.rbac.authorization.k8s.io/gitlab-nginx-ingress created
clusterrole.rbac.authorization.k8s.io/gitlab-app-role created
clusterrole.rbac.authorization.k8s.io/gitlab-manager-role created
clusterrole.rbac.authorization.k8s.io/gitlab-proxy-role created
Warning: rbac.authorization.k8s.io/v1beta1 ClusterRole is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 ClusterRole
clusterrole.rbac.authorization.k8s.io/gitlab-metrics-reader created
rolebinding.rbac.authorization.k8s.io/gitlab-leader-election-rolebinding created
rolebinding.rbac.authorization.k8s.io/gitlab-nginx-ingress created
clusterrolebinding.rbac.authorization.k8s.io/gitlab-gitlab-app-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/gitlab-gitlab-manager-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/gitlab-gitlab-proxy-rolebinding created
service/gitlab-controller-manager-metrics-service created
service/gitlab-webhook-service created
deployment.apps/gitlab-controller-manager created
Warning: cert-manager.io/v1alpha2 Certificate is deprecated in v1.4+, unavailable in v1.6+; use cert-manager.io/v1 Certificate
certificate.cert-manager.io/gitlab-serving-cert created
Warning: cert-manager.io/v1alpha2 Issuer is deprecated in v1.4+, unavailable in v1.6+; use cert-manager.io/v1 Issuer
issuer.cert-manager.io/gitlab-selfsigned-issuer created
Warning: admissionregistration.k8s.io/v1beta1 ValidatingWebhookConfiguration is deprecated in v1.16+, unavailable in v1.22+; use admissionregistration.k8s.io/v1 ValidatingWebhookConfiguration
validatingwebhookconfiguration.admissionregistration.k8s.io/gitlab-gitlab-validating-webhook-configuration created
 
❯ kubectl get all -n gitlab-system
NAME                                             READY   STATUS    RESTARTS   AGE
pod/gitlab-controller-manager-645466b464-jxn5l   2/2     Running   0          2m5s
 
NAME                                                TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/gitlab-controller-manager-metrics-service   ClusterIP   10.100.68.88     <none>        8443/TCP   2m10s
service/gitlab-webhook-service                      ClusterIP   10.100.163.107   <none>        443/TCP    2m9s
 
NAME                                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/gitlab-controller-manager   1/1     1            1           2m10s
 
NAME                                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/gitlab-controller-manager-645466b464   1         1         1       2m11s

いい感じに起動しますね、ヨシ!これからGitLab OperatorがWatchしているGitLabリソースをKubernetesにデプロイして、GitLab OperatorにGitLab本体をデプロイしてもらいましょう。

GitLabをデプロイする

GitLab Operatorをデプロイした際にGitLabリソースのCustom Resource Definition(CRD)もデプロイされています。

❯ kubectl api-resources --api-group apps.gitlab.com        
NAME      SHORTNAMES   APIVERSION                NAMESPACED   KIND
gitlabs   gl           apps.gitlab.com/v1beta1   true         GitLab

GitLab OperatorはOperator SDKのHelm Operatorで実装されています。つまりGitlabリソースのスキーマはHelmチャート(gitlab/gitlab)と同一になっています。

sdk.operatorframework.io

GitLabのHelmチャートを参考にしつつ、cert-managerとExternalDNS、NGINX Ingress Controllerと連携するGitLabリソースを作成していきます。

docs.gitlab.com

docs.gitlab.com

まずは*.ryusa.example.comワイルドカードの証明書のためCertificateリソースを作成しておきます。このリソースをあとでGitLabリソースに連携し、すべてのIngressがこのCertificateリソースを参照できるようにします。

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: gitlab-tls
  namespace: gitlab-system
spec:
  dnsNames:
  - "*.ryusa.example.com"
  issuerRef:
    group: cert-manager.io
    kind: ClusterIssuer
    name: letsencrypt
  secretName: gitlab-tls

Certificateリソースが作成され、TLS証明書が発行されたらGitLabをデプロイしましょう。

apiVersion: apps.gitlab.com/v1beta1
kind: GitLab
metadata:
  name: vulture
  namespace: gitlab-system
spec:
  chart:
    version: "5.2.4"
    values:
      global:
        hosts:
          # `gitlab` `registory` `minio`のサブドメインが生えてくる
          domain: ryusa.example.com
        ingress:
          # NGINX Ingress Controllerを使うように指定
          class: nginx
          configureCertmanager: false
          # *.ryusa.example.com ワイルドカード証明書を指定
          tls:
            secretName: gitlab-tls
      nginx-ingress:
        enabled: false
      certmanager:
        install: false

これでGitLabをKubernetes上に展開できました。(長い……)

サクッと運用してみる

それではGitLabで運用作業をやってみましょう!とはいえGitLab OperatorはDay 2 Operationを効率化するためのツールなので、バックアップやバージョンアップなどは非常にシンプルに行うことができます。

実際にコマンドを叩いて動かしてみます。

バックアップ

GitLabリソースを作成するとデフォルトでハウスキーピング用のTask-Runnerが起動します。このTask-Runnerにバックアップ含め様々な作業をお願いすることができます(らしい)。Task-RunnerのPodにはデフォルトでMinIOへの接続情報が保存されているため、Podの内部でbackup-utilityコマンドをキックするだけでバックアップを保存することができます。

docs.gitlab.com

❯ kubectl exec -it \
     -n gitlab-system task-runner-b854bfbd7-zrfgw -- backup-utility
Defaulted container "task-runner" out of: task-runner, certificates (init), configure (init)
Dumping database ... 
Dumping PostgreSQL database gitlabhq_production ... [DONE]
done
...
Dumping pages ...
empty
Packing up backup tar
[DONE] Backup can be found at s3://gitlab-backups/XX_gitlab_backup.tar

すごく簡単、これでMinIOのオブジェクトストレージにバックアップが保存されました。

MinIOのDashboard上でバックアップされたオブジェクトを確認できます

なお、GitLabリソースのTask-Runnerのサブチャートにバックアップのcron設定を追加することで定期的にバックアップを取ってくれるようになるらしいです。(未確認

apiVersion: apps.gitlab.com/v1beta1
kind: GitLab
metadata:
  name: vulture
  namespace: gitlab-system
spec:
  chart:
    version: "5.2.4"
    values:
      # 省略
      gitlab:
        task-runner:
          enabled: true
          backups:
            cron:
              enabled: true
              # 毎日朝3時にバックアップを取得
              schedule: '0 3 * * *'   

リストア

リストア……を完全自動化することはあまりないと思いますが、手動バックアップと同じようにデータをリストアすることができます。バックアップと同じようにTask-Runner Podのコンテナ上でbackup-utilityコマンドをキックするだけです。

docs.gitlab.com

❯ kubectl exec -it -n gitlab-system vulture-task-runner-7dd4d9c4bd-dgs6l \
     -- backup-utility --restore -f 'https://minio.ryusa.example.com/gitlab-backups/XXX_gitlab_backup.tar?...'
...
[DONE]
done

ただし上記コマンドでリストアできるのはデータのみであり、鍵などの秘密情報はリストアしてくれません。GitLabの再インストールなどする場合は、Secretリソースを自分でリストアする必要があります。(ドキュメントに記載あり)

GitLabアップグレード

Gitlabのバージョンをアップグレードするには、対応するHelmチャートのバージョンを変更してapplyすればOKです。あとの処理はOperatorが実施してくれます。

上記でインストールしたGitLabリソースのHelmチャートバージョンは5.2.4ですので、これを5.3.0にアップグレードにします。これによりインストールされるGitLabのバージョンが14.2.4から14.3.0に上がります。

docs.gitlab.com

apiVersion: apps.gitlab.com/v1beta1
kind: GitLab
metadata:
  name: vulture
  namespace: gitlab-system
spec:
  chart:
    # 5.2.4 -> 5.3.0
    version: "5.3.0"
    # あとは省略

kubectl apply でリソース変更後に

まとめ

GitLab Operatorを利用することでKubernetes上にGitLabを展開し、Kubernetesの言葉でGitLabを管理できるようになるところを確認しました。日時のバックアップや機能の拡張、アップデートなど日々の作業の多くをkubectlコマンドだけで管理できるようになるのは、少なくとも個人的には嬉しいですね。

今回はcert-managerやNGINX Ingress ControllerをGitLab Operator内蔵のものではないExternalなものと連携しましたが、他にもいくつかのコンポーネントをExternalに変更できるみたいです。特にPostgreSQLやObject Storageなどの状態を持つものはマネージドサービスで使いたいところですね。

docs.gitlab.com

このGitLab Operatorはまだまだ開発中とのことで、Issueを見ている限り面白そうなIssueがいくつか上がってます。(ダウンタイムなしアップグレードとか、アツい……!)

Operator: enable true Zero downtime upgrades (#59) · Issues · GitLab.org / Cloud Native / GitLab Operator · GitLab

現時点ではまだHelm版とOperator版のGitLabの間のクリティカルなギャップがあるわけではない(と思う)ですが、Operatorによる管理はHelmより柔軟な運用が可能になるのでこれからのロードマップに期待ですね。

Rancher Harvesterを使っておうちHCIを構築する話

HarvesterはRancher社が管理しているKubernetes上でHCIを構築するツールです

github.com

自宅で若干持て余している計算資源を使ってHarvesterをインストールし、Hyper-Converged Infrastructureをおうちに構築してみます

f:id:RyuSA:20210822132354p:plain
Harvesterのダッシュボード、バージョン0.2とはいえ非常にかっこいい

Hyper-Converged Infrastructure

通称HCI、これはサーバーの仮想化技術のひとつで、計算資源やネットワーキング、ストレージなどをソフトウェアベースで管理できるようにした製品のことを指すらしいです。

ja.wikipedia.org

いろいろなアプライアンス製品があるそうですが、残念ながら自分はHCI製品に触れたことがないので細かい解説ができないため詳細はWiki先生を参照ください。 詰まるところ、HCI製品を利用すると簡易なオレオレAWS/GCPが作れるといったところでしょうか?

Harvester

冒頭に話をした通り、HarvesterはRancher社がメンテナンスしているHCI製品です。Kubernetesのエコシステムをフルに活用しながら、Rancher製品をうまく組み合わせてHCIを実現しています。

Harbesterが利用しているツールとしては

  • 計算資源管理: KubeVirt
  • ネットワーク管理: Multus CNI, VLAN Plugin
  • ストレージ管理: Longhorn

が挙げられます。

Harvesterを使ってみる

実際にHarvesterをインストールして仮想マシンを作成してみます。今回の検証環境は下記の通りです。

  • 物理サーバー
    • 第6世代 Intel NUC NUC6i5SYH
  • Harvester
    • 0.2.0

ISOインストール

Harvesterはインストール方法にISOによるフルインストール(直接インストールとPXEインストールが選択可能)と既存のKubernetesにインストールするRun as Appの2種類があります。今回はまず動かしてみるということでISOを直接物理サーバーにインストールしていきます。

ISOダウンロード〜インストール完了

GithubのreleaseからISOをダウンロードしてきます。

github.com

ダウンロードしてきたISOを適当なUSBメモリに焼き込み、物理サーバーにインストールしていきます。インストール中いくつかオプションの指定ができますが、今回はすべてデフォルト値のまま進みます。

f:id:RyuSA:20210822133036p:plain
参照: https://github.com/harvester/harvester/blob/master/docs/assets/iso-install.png

トラブルなくスムーズにインストールが完了するとHarvesterのコンソールが表示されます。

f:id:RyuSA:20210822133624p:plain
Harvesterのコンソール、かっこいい

仮想マシンの起動

さっそく仮想マシンを起動してみます。コンソールに指定されているアドレスにアクセスしてダッシュボードを開きます。

f:id:RyuSA:20210822132354p:plain
Harvesterダッシュボード

仮想マシンを作成するには、事前にインストールするISOファイルとネットワークの設定が必要です。

今回はお試しにUbuntu20.04のISOを用意することに。ISOのURLを指定することになるので、ubuntu.comのURLをそのまま指定します。 ImagesタブのCreateボタンからImageの作成をします。

f:id:RyuSA:20210822143500p:plain
ImageのURLにubuntuのISOのURLを指定する

次に仮想ネットワーク、VLANを作成します。NetworksタブのCreateボタンからNetworkの作成をします。

f:id:RyuSA:20210822143717p:plain
Network作成、VLAN IDは適当に指定

これで仮想サーバーを作成する準備できました。実際に仮想マシンを作成してみます。 Virtual MachinesタブのCreateボタンから仮想マシンを作成します。今回はお試しということで、先ほど登録したUbuntuのISOとVLANを利用することにします。

f:id:RyuSA:20210822144027p:plain
仮想マシン自体の設定、デフォルトのテンプレートを使用してます

f:id:RyuSA:20210822144156p:plain
Multus CNIのおかげでPodに複数のNICが渡ります

Createボタンを押すと実際にサーバーが起動し、HarvesterのWebConsoleからUbuntuのインストール画面へアクセスすることができました。 いやーこれは非常に楽ですね!

f:id:RyuSA:20210822144520p:plain
仮想マシンのPodのNICが増え、ホスト側のネットワークから直接Podへのリーチャビリティが生まれてます

少し覗いてみる

少し動きについてみてみます。とりあえずPodの状態を確認。

~
❯ kubectl get pods                                                                                    
NAME                              READY   STATUS    RESTARTS   AGE
virt-launcher-vlan-sample-4bssz   1/1     Running   0          23h

Podの名前から見て、どうもこのPodが先ほど作成した仮想マシン本体のようです。KubeVirtの文字が輝いてますね。 でこのPodのownerReferenceを見てみると下記の通り

❯ kubectl get pods -o json virt-launcher-vlan-sample-4bssz  | jq .metadata.ownerReferences 
[
  {
    "apiVersion": "kubevirt.io/v1",
    "blockOwnerDeletion": true,
    "controller": true,
    "kind": "VirtualMachineInstance",
    "name": "vlan-sample",
    "uid": "d3cab107-362b-4dfa-a0e7-122457f719ac"
  }
]

なるほど、この仮想マシンはkubevirt.io/v1のVirtualMachineInstanceで作成されているとのこと。 Harvesterの領域ではなくKubeVirtの領域になりますがもう少し深掘りしてみます。このVirtualMachineInstanceのownerReferenceを覗いてみると下記の通り

❯ kubectl get VirtualMachineInstance -o json vlan-sample | jq .metadata.ownerReferences
[
  {
    "apiVersion": "kubevirt.io/v1",
    "blockOwnerDeletion": true,
    "controller": true,
    "kind": "VirtualMachine",
    "name": "vlan-sample",
    "uid": "b6d42e46-0004-47a3-a86d-78411d878cfc"
  }
]

つまりVirtualMachineを作成することでVirtualMachineInstanceが作成され、そのVirtualMachineInstanceに合わせてPodが起動する感じでしょうか? kubectl explainで覗いてみる限り

という関係性っぽいですね。なるほどー

コードを読んでみると、どうもHarvesterはどんなVirtualMachineオブジェクトでも管理対象とするみたいですね。つまりマニュアルで仮想マシンを作ることができそうかな……?

github.com

ためしに下記のようなマニフェストで作成してみます。(ちょっと長い)

apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: manual-vm
spec:
  running: true
  dataVolumeTemplates:
  - apiVersion: cdi.kubevirt.io/v1beta1
    kind: DataVolume
    metadata:
      annotations:
        harvesterhci.io/imageId: default/image-9dk4s
      name: manual-vm-cdrom-disk
    spec:
      pvc:
        accessModes:
        - ReadWriteMany
        resources:
          requests:
            storage: 10Gi
        storageClassName: longhorn-image-9dk4s
        volumeMode: Block
      source:
        blank: {}
  - apiVersion: cdi.kubevirt.io/v1beta1
    kind: DataVolume
    metadata:
      name: manual-vm-rootdisk
    spec:
      pvc:
        accessModes:
        - ReadWriteMany
        resources:
          requests:
            storage: 10Gi
        storageClassName: longhorn
        volumeMode: Block
      source:
        blank: {}
  template:
    metadata:
      annotations:
        harvesterhci.io/diskNames: '["manual-vm-rootdisk","manual-vm-cdrom-disk"]'
        harvesterhci.io/sshNames: '["github.com"]'
      labels:
        harvesterhci.io/creator: RyuSA
        harvesterhci.io/vmName: manual-vm
    spec:
      domain:
        cpu:
          cores: 1
        devices:
          disks:
          - bootOrder: 2
            cdrom:
              bus: sata
            name: cdrom-disk
          - bootOrder: 1
            disk:
              bus: virtio
            name: rootdisk
          - disk:
              bus: virtio
            name: cloudinitdisk
          interfaces:
          - macAddress: 52:54:00:57:4c:53 #適当
            masquerade: {}
            model: virtio
            name: default
          - bridge: {}
            macAddress: 2e:61:8d:3d:35:4c #適当
            model: virtio
            name: vlan
        machine:
          type: q35
        resources:
          requests:
            memory: 2Gi
      evictionStrategy: LiveMigrate
      hostname: manual-vm
      networks:
      - name: default
        pod: {}
      - multus:
          networkName: example-vlan
        name: vlan
      volumes:
      - dataVolume:
          name: manual-vm-cdrom-disk
        name: cdrom-disk
      - dataVolume:
          name: manual-vm-rootdisk
        name: rootdisk
      - cloudInitNoCloud:
          userData: |
            #cloud-config
            ssh_authorized_keys:
              - >-
                ssh-rsa (snip)
        name: cloudinitdisk

これでkubectl createでオブジェクトを作成してみると

f:id:RyuSA:20210823222659p:plain
Cliでも仮想マシンを作成できた

一部の部分(labelやannotation)のお作法さえ守れば、しっかり動きますね。まぁできたからと言ってなにか良いことがあるわけではないのですが……w

まとめ

Harvesterを利用して、Kubernetesクラスタ上にHCIを構築して仮想マシンKubernetesリソースベースで作成してみました。HarvesterのGUIで指定できる項目がまだ少なかったりしましたが、簡単な用途であれば現在のバージョンでもいい感じに動かせると思います。

本当は既存のKubernetes上にデプロイすることまでやりたかったのですが、手元にあるKuberenetesクラスターはRaspberry Piで構築していたため今回は断念。 というのもKubeVirtのARM64のサポートはまだ完成しておらず、それに引きずられてHarvesterのARM64サポートはこれからの状態です。

github.com

なので、気長に待ちつつ、可能なら貢献して行こうかなと思います。

Raspberry Pi上にWi-Fi APを自動構築するKubernetes Operatorを作った話

元ネタ、というよりWhy/作ったモチベはこちらに書いてあります👀

eng-blog.iij.ad.jp

雑に言えば「自宅にメッシュWi-Fiを導入したかったので、Raspberry PiWi-Fi APを実装してみた」という感じ。

TL;DR

最終形として、こんなOperatorを作成しました。

f:id:RyuSA:20210702181459p:plain

github.com

これによりCRDであるAccessPointリソースをKubernetesにデプロイすることで、KubernetesにjoinしているRaspberry Pi上に適切に設定されたWi-Fiアクセスポイントを作成することができるようになりました。

これでkubectlひとつでメッシュなWi-Fiを構築したり来客向けのゲストWi-Fiを用意することができるようになりました、やったぜ✨

Rapsberry PiのAP化 #とは

Raspberry PiWi-FiのAPとして動かせる話は昔からあり、いろんな方が紹介してくださってます🥰

qiita.com

qiita.com

つまるところRaspberry Piのワイヤレスのネットワークインタフェースとイーサネットのネットワークインタフェースをブリッジでつなぎ、ワイヤレスネットワークインタフェースをAPモードで起動しようということですね。ソフトウェアとしてはhostapdがよく使われているみたいです🤔

Raspberry Pi自体は安価に調達できて、かつPoE Hatを利用することでLANケーブル一本で動かすことができるなど取り回しが良さそう……今回やりたいことにピッタリ。

hostapd

hostapdはネットワークインターフェースカードをアクセスポイントとして動かすためのソフトウェアで、設定ファイルを咬まして起動することで指定したSSID/パスワード/その他設定のアクセスポイントを起動することができます。すごく便利👏

とりあえず手動オペレーション💪

一度、手動でWi-Fi APをRaspberry Pi上に展開するための手順を確認してみます。

  1. ネットワーク内にDHCPを用意する
  2. Raspberry Piのネットワークインタフェースを変更する(ブリッジ作ったり)
  3. Raspberry Piにhostapdをインストール、デーモンとして登録する
  4. configファイルを作成する
  5. configファイルにSSIDとパスワード、その他設定を入れる
  6. デーモンのリロード&再起動

手動オペレーションの場合、この手順をAPにしたいRaspberry Piすべてに実施していくことになります。実際に自宅に転がってたRaspberry Pi上でオペレーションしてみたところ、1つあたり新規作成に1時間程度時間がかかりました。これで運用するのは……かなりいやですね🤔

hostapdのインストールの手順そのものはコンテナを配布することで解決自体はできます。ついでだったのでコンテナイメージを作りました

github.com

しかし、コンテナを起動したりconfigファイルを配布したりと運用作業を真心込めて1台1台手作業するのはちょっとありえないという気持ち🤮

Operatorにしていく🦾

ルーチンな運用作業はKubernetesのOperatorにするに限る。ということでOperatorで実装することを決意しました。

Kubernetes Operatorとは、KubernetesAPIリソースを監視して「現在の状況」と「理想の状況」を計算し、現在の状況をあるべき姿にドリブンしていくアプリケーションのことを指します。通常、後述するCRDと共に実装を行い日々の運用の自動化などを行うことが多いです。

有名なOperatorとしてはPrometheusのデプロイや設定をKubernetesリソースで行えるようにしたPrometheus Operatorや、RabbitMQのクラスターデプロイをKubernetesリソースで定義できるようにしたRabbitMQ Cluster Operatorなどがあります。

github.com

github.com

スコープ決め

手動オペレーションの各タスクのうち、自動化できそうな範囲を考えてみます。

  1. ネットワーク内にDHCPを用意する
    • これは各ネットワーク内で決定するべき事柄、ここまで責任を持つのは過剰
    • → スコープ外
  2. Raspberry Piのネットワークインタフェースを変更する
    • これはcloud-initを利用して自動化(=k8s関係ない範囲)
    • → スコープ外
  3. Raspberry Piにhostapdをインストール、デーモンとして登録する
    • 指定したノードにPodとして起動すれば……?
    • → DaemonSetとして配布します
  4. configファイルを作成する
    • 最低限の設定を外から渡せればConfigMapに入れるところまで自動化できそう
    • → CRDとして定義し、そこからConfigMapを作成&DaemonSetと一緒に配布します
  5. configファイルにSSIDとパスワード、その他設定を入れる
    • 4.と同様
  6. デーモンのリロード&再起動
    • Kubernetesの機能をそのまま利用します
    • → 実装しない(DaemonSetに任せる)

ということで、1.と2.以外のタスクを自動化する方針で実装していきます。

Kubebuilder

Operatorを実装するにあたり、ほしいのは"Kubernetesのエコシステム"であってOperatorのフルスクラッチではないです🤔

Kubernetes Operatorを実装するためのフレームワークとして有名なものが2つあります。

  • Kubebuilder
  • Operator SDK

それぞれを触ってみた感じ、個人的にはKubebuilderの方が使いやすそうに感じたので今回はKubebuilderを利用して実装していこうと思います🥰

こちら参考書籍です。KubernetesのOperatorとはなにか、KubebuilderとOperator SDKの違いとサンプル実装と非常にわかりやすく解説されててマジで助かりました。

CRD

KubernetesにはCuston Resource Definitionというリソースの拡張機能が存在しており、いわば「俺専用のKubernetesリソースを作ってやるぜ〜〜^^」ができます。CRD自体はKubernetes 1.16でGAとなっているAPIです。

今回のパターンでは2種類のCRDを実装しました。

  • AccessPoint
    • アクセスポイントそのものを表すリソース
    • SSIDの設定やパスワード、あと細かい設定とかと記述する
  • AccessPointDevice
    • アクセスポイントをデプロイする仮想的なデバイス
    • ブリッジの設定や操作するネットワークインタフェースなどを記述する
    • 用途としては"Aliceの部屋"とか"1F リビング"とかの単位で書いていく

ストーリーとしては

  • まずユーザーがAccessPointDeviceをデプロイ
  • ユーザーがAccessPointDeviceを指定したAccessPointをデプロイ
  • AccessPoint OperatorがAccessPointの作成イベントを受信
  • AccessPointDeviceと合わせてAccessPoint Operatorが適切なDaemonSetを作成してデプロイ
  • DaemonSetの力で各Raspberry Pi上にhostapdの設定とhostapdコンテナがデプロイされていく

……といった流れでリソースを操作していくかな?と考えています。

最終的に出来上がったもの

開発そのものについては(ただ実装しただけなので)割愛します。興味がある人が多そうであればどこかに書くなりしておきます。

最初にも紹介しましたが、こんな感じに完成しました。細かい使い方等は一応READMEに書いてあります。なお現時点ですでに可愛いバグ(AccessPointDevice消してもDaemonSetが消えない等)があったりするのは許してください。。。

f:id:RyuSA:20210702181459p:plain

github.com

開発の環境としては使い捨てできるKubernetesを作成するためのkindを利用しました。便利ですね〜

github.com

以前構築したRaspberry Piクラスターのネットワーク設定を変更して……

ryusa.hatenablog.com

作成したOperatorをデプロイしてみると……

f:id:RyuSA:20210703233015p:plain
Operatorで作成した"RASPBERRY" APに繋がった

手元のスマホからWi-Fiに接続&インターネットへアクセスできました!やったぜ🤪

ためしにhostapdがデプロイされている環境でRaspberry Piをインストールしてある部屋から部屋へ移動してみましたが、同じSSID/パスワードでうまくローミング(?)してシームレスに接続を継続してくれました。これはいい感じのモノが作れた……!

完走した感想

実はGo言語でがっつり開発したのは初挑戦だったので色々雑な作りになってしまいました。が、それでも今日も自宅で動いてくれています🥰毎日SSIDとパスワードを変えたい気持ちになりますねw

一方でKubebuilder側がまだM1 Mac(自分の開発環境)に正式対応しておらず、Operatorのテスト機構については全く手が出せていない状況です。ARM対応の実装を自分ですれば良いだけなのですが、なかなか食指が動かず……orz

定期的に開発できたらいいな〜と思ってます。