Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kube API Server(k8sjp)

bells17
September 28, 2021

Kube API Server(k8sjp)

bells17

September 28, 2021
Tweet

More Decks by bells17

Other Decks in Programming

Transcript

  1. Kube API Server
    Kubernetes Meetup Tokyo #45(2021/09/28)
    @bells17

    View Slide

  2. ▶ @bells17
    ▶ Software Engineer@IDC Frontier inc.
    ▶ Kubernetes SIG-Docs Japanese localization reviewer
    ▶ Kubernetes Internal Organizer
    ▶ #kubenews
    ▶ @bells17_

    View Slide

  3. View Slide

  4. 毎週⾦曜⽇22:00~ k8s・Cloud Native関連のニュース紹介配信してます

    View Slide

  5. 今⽇話すこと
    ▶ Kubernetes Core Componentについての簡単な説明
    ▶ Kube API Serverの⼤まかなアーキテクチャと⼀部機能の実装について

    View Slide

  6. 注意点
    ▶ Kubernetes v1.21.2ベースでのお話になります
    ▶ KubernetesそのものやKubernetes Component全体の説明は簡単なものに
    なるかと思います
    ▶ あくまでKube API Serverの実装を追った結果での理解の説明になるので、
    間違ってる箇所があったら教えて下さい

    View Slide

  7. そもそもKubernetesとは?

    View Slide

  8. Kubernetes とは?
    ▶ Kubernetesはコンテナオーケストレーターの1つ
    ▶ etcd/control plane/worker nodeによって構成されたクラスターを構築し、
    様々なコンテナをKubernetes上のnodeで動作させたり、動作させてるコン
    テナとネットワークをいい感じに連携できるようにすることができる
    ▶ デプロイするコンテナなどをmanifestファイルで宣⾔的に記述することで、
    宣⾔した状態になるようにKubernetesがいい感じに調整処理を⾏ってくれる
    ▶ Googleが内部で運⽤していたコンテナ基盤であるBorgをOSS向けに作り直
    したコンテナオーケストレーター
    ▶ また、KubernetesはCloud Native Computing Foundation(CNCF)に寄贈さ
    れており、CNCFのGraduatedプロジェクトとしてコミュニティベースで管理
    されている

    View Slide

  9. manifestをKubernetesに適⽤することで
    宣⾔されたmanifestの通りにコンテナが作成される

    View Slide

  10. Kubernetes Core Component

    View Slide

  11. https://github.com/kubernetes/website/blob/fb6364da0afd19e8a9515aaae2de9bc74a0a6abd/static/images/docs/components-of-kubernetes.png

    View Slide

  12. Core Component
    ▶ etcd: API Serverのバックエンドで使⽤されている分散型のKVS
    ▶ Control Plane
    ▶ API Server: KubernetesのAPIリクエストを処理するサーバー
    ▶ Kube Controller Manager: Kubernetesの様々なリソースのためのロジックを動かす
    様々なコントローラーを動作させるマネージャー
    ▶ Cloud Controller Manager: Kubernetesとクラウド(実⾏基盤)を連携するための
    コントローラーを動作させるマネージャー
    ▶ Scheduler: PodをどのWorker Nodeに配置するかを決める
    ▶ Worker Node
    ▶ Kubelet: Worker Nodeで実⾏するコンテナを管理するアプリケーション
    ▶ Kube Proxy: Serviceリソースに基づくネットワーク設定を⾏うアプリケーション

    View Slide

  13. ▶ Cloud Controller Manager Deep Dive
    ▶ Kubernetes Internal #2
    Cloud Controller Managerについては以下のスライドで
    まとめてあるので参考にしてください

    View Slide

  14. ▶ Kubeletから読み解くKubernetesのコンテナ管理の裏側
    ▶ Kubernetes Internal #4
    ▶ Kubeletから読み解くKubernetesのコンテナ管理の裏側
    Kubeletについても以前発表しているセッションや書籍が
    あるので良ければ参考にしてください

    View Slide

  15. https://github.com/kubernetes/website/blob/fb6364da0afd19e8a9515aaae2de9bc74a0a6abd/static/images/docs/components-of-kubernetes.png

    View Slide

  16. Kube API Serverとは?

    View Slide

  17. Kube API Server
    ▶ Kubernetesの様々なデータ保存や取得、イベント通知を⾏うAPI Server
    ▶ Rest APIを中⼼としたAPI Serverを提供する
    ▶ API定義はProtocol Buffersによって⾏われる
    ▶ 定義したAPIはAPI Server側でOpenAPI形式に変換され、APIからスキーマ定義が
    取得可能
    ▶ etcdをデータ永続のためのデータストアとして使⽤
    ▶ 独⾃リソース管理のための機能を提供
    ▶ Extension API Server
    ▶ Custom Resource Definition(CRD)

    View Slide

  18. まずはイメージを掴むために軽く触ってみる

    View Slide

  19. Kube API Serverを触ってみる(まずはkubectlで)
    1PEҰཡΛऔಘ͢Δ
    LVCFDUMOEFGBVMUHFUQPE
    /".&3&"%:45"5643&45"354"(&
    NZQPE3VOOJOHI

    View Slide

  20. Kube API Serverを触ってみる(次にcurlで)
    LVCFQSPYZίϚϯυͰ"1*4FSWFSʹ௚઀DVSMͰϦΫΤετΛͰ͖ΔΑ͏ʹ
    LVCFDUMQSPYZŠQPSU
    DVSMͰEFGBVMUOBNFTQBDFʹ͋Δ1PEҰཡΛऔಘ
    DVSMIUUQMPDBMIPTUBQJWOBNFTQBDFTEFGBVMUQPET
    \
    LJOE1PE-JTU
    BQJ7FSTJPOW
    NFUBEBUB\
    SFTPVSDF7FSTJPO
    ^
    JUFNT<
    \
    NFUBEBUB\
    OBNFNZQPE
    OBNFTQBDFEFGBVMU

    View Slide

  21. どんなリソース定義があるのかを調べることができたり
    DVSMIUUQMPDBMIPTUBQJW
    \
    LJOE"1*3FTPVSDF-JTU
    HSPVQ7FSTJPOW
    SFTPVSDFT<

    \
    OBNFQPET
    TJOHVMBS/BNF
    OBNFTQBDFEUSVF
    LJOE1PE
    WFSCT<
    DSFBUF EFMFUF EFMFUFDPMMFDUJPO HFU MJTU QBUDI VQEBUF XBUDI
    >
    TIPSU/BNFT<
    QP
    >
    DBUFHPSJFT<
    BMM
    >
    TUPSBHF7FSTJPO)BTIY10X3;:IX
    ^

    View Slide

  22. そもそもどんなパスがあるのかを調べたりすることもできる
    DVSMIUUQMPDBMIPTU
    \
    QBUIT<
    XFMMLOPXOPQFOJEDPOpHVSBUJPO
    BQJ
    BQJW
    BQJT
    BQJT

    lIFBMUI[ "1*4FSWFSͷϔϧενΣοΫΤϯυϙΠϯτ
    ʜ
    lNFUSJDT "1*4FSWFSͷϝτϦΫεΛऔಘ
    lPQFOBQJW "1*4FSWFSʹ0QFO"1*εΩʔϚΛऔಘ ΊͬͪΌ௕͍

    PQFOJEWKXLT

    lWFSTJPO"1*4FSWFSͷόʔδϣϯΛऔಘ
    >
    ^

    View Slide

  23. こんな感じでcurlで叩いて⾒ると普通の
    API Serverっぽい感じを実感できる

    View Slide

  24. API Serverにリクエストしたときに
    起きてること
    ▶ http2によるリクエストをAPI Serverが受信
    ▶ API ServerのServerChainにより様々な前処理を実⾏
    + CORS/HSTSなど各種HTTPヘッダーを設定
    + 認証/認可処理
    + なりすまし機能によるユーザー情報の差し替え
    + etc
    ▶ 対象リソースの取得/作成など実⾏
    + リソースの作成や更新時は各種Admission Controllerによる値の上書き
    やバリデーションを実⾏
    + バリデーションなどでエラーが無ければetcdにデータを保存
    ▶ レスポンスを返す

    View Slide

  25. ということでKube API Serverの
    アーキテクチャを⾒ていく

    View Slide

  26. Kube API Server Overview

    View Slide

  27. ServerChain

    View Slide

  28. ServerChain

    View Slide

  29. ServerChain
    ServerChainは各リクエストの処理前に実⾏される前処理になる
    ▶ HSTS/CacheControl/CORSといったHTTP headerを設定
    ▶ リクエスト処理時間を記録
    ▶ リクエスト⽤のaudit eventレコーダーを⽣成
    ▶ リクエスト情報を元にRequestInfoオブジェクトを⽣成
    ▶ リクエストの種類を元にタイムアウト時間を設定
    ▶ 認証
    ▶ ユーザーのなりすまし設定
    ▶ APIリクエストの優先度コントロール
    ▶ 認可
    といったことを⾏なっている
    これらの前処理後に実際に要求した各種のリソース操作などの処理を⾏なっている

    View Slide

  30. Kube API Serverのアーキテクチャ
    Kube API Serverは主に3つのコンポーネントによって構成されている
    ▶ API Extentions Server
    ▶ API Server
    ▶ Aggregator Server

    View Slide

  31. API Extensions Server

    View Slide

  32. API Extensions Server
    ▶ API Extentions Serverは、ユーザーが作成するCRDリソースの設定に
    基づいてカスタムリソースを処理するためのhttp handlerを動的に提供する
    機能を提供する
    ▶ そのため、CRDが作成されるとAPI Extentions Server内部で動いている
    Kubernetes Controller群によってバリデーションなどのチェックが⾏わ
    れ、問題無ければ動的にカスタムリソースを処理するエンドポイントが提供
    される
    ▶ つまり、Kubernetesを使っててよくお世話になるKubernetes Operatorが
    機能するのはこのAPI Extensions Serverのおかげ
    ▶ また、CRDリソースのCRUDエンドポイントを⽤意してるのもこのサーバー

    View Slide

  33. API Server

    View Slide

  34. API Server
    ▶ API Serverは、Kubernetes内部で定義済みのPodやConfigMapといった
    各種リソースを処理するためのAPIサーバーを提供する
    ▶ なのでKubernetesがデフォルトで提供するリソースを処理するためのエン
    ドポイントはだいたいこのAPI Serverが提供してくれている
    ▶ ちなみに定義済みのAPIは⼤まかに以下のような感じで分類できるっぽい
    ▶ Core(Legacy) API: PodやConfigMapなど”v1”のAPI
    ▶ GroupVersion API:“batchv1”や”appsv1”などのグループに所属してるAPI
    ▶ その他のAPI: VersionやHealthzなどのAPI

    View Slide

  35. Aggregator Server

    View Slide

  36. Aggregator Server
    ▶ Aggregator Serverは初期化時にAPI ServerとAPI Extensions ServerのOpenAPIス
    キーマ定義を読み取り、APIServiceリソースを⽣成する
    ▶ Aggregator ServerではAPIServiceリソースを監視するKubernetes Controllerが動
    作していて、APIServiceリソースの変更に基づいて、動的にリクエストを処理する
    ためのhttp handlerの設定が⾏われる
    ▶ また、ユーザーが⼿動で追加したExtension API Serverについては、ユーザーが⼿
    動でAPIServiceリソースを作成することにより、設定した条件に基づいてリクエス
    トがExtension API Serverにプロキシされる
    ▶ API Extentions ServerやAPI Serverは、実際にはAggregator Serverに組み込まれて
    動作するので、複数のHTTPサーバーが起動するわけではない
    (それぞれが別々に⾃⽴して起動できるような実装にはなっているよう)

    View Slide

  37. Extension API Server
    ▶ Kube API Serverには独⾃のAPI Serverを構築し、Kube API Serverと連携
    する仕組みがある
    ▶ この独⾃に構築したAPI ServerのことをExtension API Serverと呼ぶ
    (少なくとも公式ドキュメントではそういう呼び⽅してる)
    ▶ API Extensions Servertと似たような名前でわかりづらい
    ▶ Extension API Serverを構築 → APIServiceを登録することで連携される
    ▶ Extension API Serverを構築する⽅法として以下のような⽅法が提供されて
    いる
    + https://github.com/kubernetes/sample-apiserver
    + https://github.com/kubernetes-sigs/apiserver-builder-alpha

    View Slide

  38. Extension API Serverの利⽤例として
    カスタムメトリクスをAPI Serverとして提供する例などがある
    https://github.com/kubernetes-sigs/custom-metrics-apiserver

    View Slide

  39. 先程のcustom-metrics-apiserverを利⽤した例
    LVCFDUMHFU1PE.FUSJDTNZQPEPZBNM
    BQJ7FSTJPONFUSJDTLTJPWCFUB
    DPOUBJOFST
    OBNFVCVOUV
    VTBHF
    DQV
    NFNPSZ,J
    LJOE1PE.FUSJDT
    NFUBEBUB
    DSFBUJPO5JNFTUBNQ5;
    MBCFMT
    BQQLVCFSOFUFTJPOBNFNZQPE
    OBNFNZQPE
    OBNFTQBDFEFGBVMU
    UJNFTUBNQ5;
    XJOEPXNT

    View Slide

  40. Admission ControllerとAdmission Webhook

    View Slide

  41. Admission Controller
    ▶ Kube API ServerにはAdmission ControllerというAPI Serverへのリソース
    の保存や更新時に、保存するデータを上書きしたり、バリデーションする
    仕組みが存在する
    ▶ Admission ControllerはKube API Serverに組み込まれており、プラグイン
    という形でどのAdmission Controllerを利⽤するかを選択することができる
    ▶ この機能は、元々OpenShiftに実装されていたものが、Kubernetesに移植
    される形で導⼊されたらしい

    View Slide

  42. NamespaceLifecycle
    削除中のNamespaceへのリソース作成を防⽌したり
    システム⽤のNamespaceの削除防⽌を⾏なったりする
    LimitRanger
    Namespace内のコンテナのリソースリミット/リクエストの設定を強制する
    Namespace内のPVCのリソースリミット/リクエストの設定を強制する、など
    TaintNodesByCondition 新しく追加されたNodeに対して”NotReady”や”NoSchedule”のtaintを⾃動で付与する
    PersistentVolume
    ClaimResize
    Volume拡張が許可されていないStorageClassを使ったPVCの容量アップを防⽌する
    Admission Pluginの例
    その他にも合計で35種類ほどのPluginがあり、そのうち18種類がデフォルトで有効になってる

    View Slide

  43. Admission Webhook
    ▶ Admission Pluginの⼀種である
    + ValidatingAdmissionWebhook
    + MutatingAdmissionWebhook
    ▶ の2つのpluginが提供する機能
    ▶ それぞれ
    + MutatingWebhookConfiguration
    + ValidatingWebhookConfiguration
    ▶ というリソースを設定することで独⾃のWebhookサーバーを通してデータ
    の上書き/バリデーションを⾏うことが可能
    ▶ データの上書き/バリデーションの機能が利⽤できるので、実質的に各種
    Admission Pluginと同じ仕組みをKubernetesの外部から提供可能

    View Slide

  44. Kubebuilderによる
    Admission Webhookの提供
    ▶ KubebuilderはKubernetesコミュニティが提供する、Kubernetes Operator/
    Admission Webhookを実装するためのフレームワーク
    ▶ Kubebuilderを利⽤することで、簡単にAdmission Webhookを実装~提供が
    可能
    ▶ Webhookサーバーのために⽣成する⾃⼰証明書の⽣成がcert-managerに依存
    しているので、cert-managerのインストールが必要となっている点に注意

    View Slide

  45. https://book.kubebuilder.io/cronjob-tutorial/webhook-implementation.html
    KubebuilderによるValidating Webhook実装例

    View Slide

  46. etcdにデータが保存されるまで

    View Slide

  47. View Slide

  48. etcdにデータが保存されるまで
    ▶ ServerChainによる前処理を実⾏
    ▶ HTTP Bodyをデコードしてruntime.Objectを⽣成
    ▶ オブジェクトにManagedFieldを設定
    ▶ 各種MutatingAdmissionを実⾏
    + 対象リソースを処理するMutatingWebhookが登録されている場合は実⾏
    ▶ オブジェクトにOwnerReferenceを設定
    ▶ 各種ValidatingAdmissionを実⾏
    + 対象リソースを処理するValidatingAdmissionが登録されている場合は実⾏
    ▶ 対象リソースに応じたTransformerを実⾏し、etcdに保存するデータを暗号化
    + 暗号化にKMSプロバイダーを使⽤している場合には、unix domain socketとgRPCを通してKMS
    プラグインへリクエスト
    + KMSプラグインを通してKMSプロバイダーのAPIを呼び出し
    + リソースごとの暗号化設定はEncryptionConfigurationによって設定可
    ▶ etcdへデータを保存
    ▶ デコードしてクライアントへレスポンスを返す

    View Slide

  49. Etcdのデータを⾒てみると実際に暗号化されてるのが確認できる
    LVCFDUMFYFDFUDEDPOUSPMQMBOFOLVCFTZTUFNJUTID&5$%$5-@"1*FUDEDUMDBDFSUFUDLVCFSOFUFTQLJFUDEDBDSUDFSUFUD
    LVCFSOFUFTQLJFUDETFSWFSDSULFZFUDLVCFSOFUFTQLJFUDETFSWFSLFZXpFMETHFUSFHJTUSZQPETLVCFTZTUFNFUDEDPOUSPMQMBOF
    $MVTUFS*%
    .FNCFS*%
    3FWJTJPO
    3BGU5FSN
    ,FZSFHJTUSZQPETLVCFTZTUFNFUDEDPOUSPMQMBOF
    $SFBUF3FWJTJPO
    .PE3FWJTJPO
    7FSTJPO
    7BMVFLTaYaOaUaOaYWaYaY1PEaYaYDBaOaYBaYaOaFUDEDPOUSPMQMBOFaYaYaYBaWLVCF
    TZTUFNaaYGDCED
    CCCDEFaYaY#aCaCaYGDaYBBaYCaYaYaYaY;aYaOaUDPNQPOFOUaYaYFUDE;aYaOaYUJFSaYaSDPOUSPM
    QMBOFC,aOLVCFBENLVCFSOFUFTJPFUDEBEWFSUJTFDMJFOUVSMTaYaYIUUQTCaOaYLVCFSOFUFTJPDPOpHIBTIaY
    CFEBBFGC⒎EEBC aOaYCLVCFSOFUFTJPDPOpHNJSSPSaYCFEBBFGC⒎EEBC
    aOaYLVCFSOFUFTJPDPOpHTFFOaYaYF5;CaOaYCLVCFSOFUFTJP
    DPOpHTPVSDFaYaYpMFK2aOaY/PEFaYBaYEDPOUSPMQMBOFazEFGCECGFD
    DGEFFGEaYWaY[aYaYBaYaYDaYaOaBLVCFMFUaYaY6QEBUFaYBaYWaaCaCaYaYBCaYCaYaYaYaYaC'JFME
    T7aYFaYaOaYEEaY
    ʜ

    View Slide

  50. aws-encryption-providerの実装例
    https://github.com/kubernetes-sigs/aws-encryption-provider/blob/959eca3a6354703db2529219bedeebee35599026/pkg/plugin/plugin.go

    View Slide

  51. View Slide

  52. etcdからデータを取り出す際
    ▶ ServerChainによる前処理を実⾏
    ▶ HTTP queryをデコードしてOptionを⽣成
    ▶ Optionの条件などを元にetcdからデータを取得
    ▶ 対象リソースに応じたTransformerを実⾏し、etcdに保存するデータを暗号化
    + 暗号化にKMSプロバイダーを使⽤している場合には、unix domain socketと
    gRPCを通してKMSプラグインへリクエスト
    + KMSプラグインを通してKMSプロバイダーのAPIを呼び出し
    ▶ デコードしてクライアントへレスポンスを返す

    View Slide

  53. まとめ

    View Slide

  54. まとめ
    ▶ Kube API ServerはAggregator Serverによって、対象のリソースを処理するAPI Serverへプロ
    キシを⾏い、リクエストを処理するというアーキテクチャになっていました
    ▶ APIServiceというリソースによってAPI Extention Server, API Server, ユーザー独⾃の
    Extention API Serverのどこにリクエストをプロキシするかを判定してます
    ▶ リクエストを受けるとServerChainという様々な前処理によって認証認可などの処理が⾏われ
    ています
    ▶ Admission Controller(Plugin)によってリクエストデータのMutating/Validatingを⾏う仕組み
    があり、どのPluginを有効/無効にするかを設定することができるようになっています
    ▶ Admission Webhookを利⽤することでユーザー独⾃のMutating/Validating処理が実⾏可能な
    仕組みが⽤意されてます
    ▶ 実際にetcdにデータが保存される際にはEncryptionConfigurationに基づいた暗号化処理が⾏
    われており、暗号化の際に外部のKMS Providerを利⽤するためにはKMS Pluginが必要でした

    View Slide

  55. 参考資料
    ▶ Kubernetes v1.21.2: https://github.com/kubernetes/kubernetes/tree/v1.21.2
    ▶ Kubebuilder Book: https://book.kubebuilder.io/
    ▶ sample-controller: https://github.com/kubernetes/sample-controller
    ▶ apiserver-builder-alpha: https://github.com/kubernetes-sigs/apiserver-builder-alpha
    ▶ Custom Metrics Adapter Server Boilerplate: https://github.com/kubernetes-sigs/custom-metrics-apiserver
    ▶ Prometheus Adapter for Kubernetes Metrics APIs: https://github.com/kubernetes-sigs/prometheus-adapter
    ▶ metrics: https://github.com/kubernetes/metrics
    ▶ CRI Streaming Requests (exec/attach/port-forward): https://docs.google.com/document/d/1OE_QoInPlVCK9rMAx9aybRmgFiVjHpJCHI9LrfdNM_s/edit#heading=h.4yfjiw58o8d3
    ▶ Using a KMS provider for data encryption: https://kubernetes.io/docs/tasks/administer-cluster/kms-provider/
    ▶ Encrypting Secret Data at Rest: https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/
    ▶ Adding custom resources to the Kubernetes API server: https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/extending-api.md
    ▶ Use an HTTP Proxy to Access the Kubernetes API: https://kubernetes.io/docs/tasks/extend-kubernetes/http-proxy-access-api/
    ▶ Control Plane-Node Communication: https://kubernetes.io/docs/concepts/architecture/control-plane-node-communication/
    ▶ Dynamic Admission Control: https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/
    ▶ Kubernetes Proposal - Admission Control: https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/admission_control.md
    ▶ Extension of Admission Control via Initializers and External Admission Enforcement: https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/
    admission_control_extension.md
    ▶ Webhook Bootstrapping: https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/admission-webhook-bootstrapping.md
    ▶ Webhooks Beta: https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/admission-control-webhooks.md
    ▶ Dynamic Admission Control: https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers
    ▶ Configure the Aggregation Layer: https://kubernetes.io/docs/tasks/extend-kubernetes/configure-aggregation-layer/
    ▶ Set up an Extension API Server: https://kubernetes.io/docs/tasks/extend-kubernetes/setup-extension-api-server/
    ▶ Vanilla CRD OpenAPI subset: structural schemas: https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/2335-vanilla-crd-openapi-subset-structural-schemas
    ▶ The Kubernetes API: https://kubernetes.io/docs/concepts/overview/kubernetes-api/
    ▶ Controlling Access to the Kubernetes API: https://kubernetes.io/docs/concepts/security/controlling-access/
    ▶ Authenticating: https://kubernetes.io/docs/reference/access-authn-authz/authentication/
    ▶ Authorization Overview: https://kubernetes.io/docs/reference/access-authn-authz/authorization/

    View Slide

  56. まとめ資料
    ▶ Zenn scrap: https://zenn.dev/bells17/scraps/81b6ade4cbd40d

    View Slide

  57. 概要に加えて実装についての説明
    はこちらの本で⾏なってます
    ▶ 技術書典: https://bit.ly/tbf-kube-api-server
    ▶ Booth: https://bit.ly/booth-kube-api-server
    Kube API Serverの詳細が気になる⽅は
    良ければ買ってください!

    View Slide

  58. Thanks / Question?
    ▶ @bells17
    ▶ Slide: https://speakerdeck.com/bells17
    ▶ @bells17_

    View Slide