kun432's blog

Alexaなどスマートスピーカーの話題中心に、Voiceflowの日本語情報を発信してます。たまにAWSやkubernetesなど。

〜スマートスピーカーやVoiceflowの記事は右メニューのカテゴリからどうぞ。〜

いまさらながらGKEを試す

f:id:kun432:20210920165841p:plain

Kubernetesについては、AWSではEKS、オンプレではkubeadmを使ってきましたが、そういえばGKEは一度も使ったことがない。ということで、少し触ってみました。

目次

クラスタの作成

GCPのコンソールから、"Kubernetes Engine" -> "クラスタ"をクリック。

f:id:kun432:20210920170144j:plain

初回の場合はAPIを有効化します。

f:id:kun432:20210920170243j:plain

"作成"をクリック。

f:id:kun432:20210920170554j:plain

クラスタの作成には2種類の方法があります。"GKE Autopilot"は、worker nodeをGCP側で管理してくれて勝手にスケーリングなどが行われるというようなものみたい。EKS Fargetみたいな感じですかね(Fargate触ったことないし、知らんけど)。とりあえず"GKE Standard"を選択します。

f:id:kun432:20210920170644j:plain

ここから作成するクラスタの設定を行っていきます。今回はあまり細かい設定は行わずにほぼデフォルトでクラスタ作って試してみるつもりなのですが、どのような設定項目があるのかは順に見ていきたいと思います。

クラスタの基本

まず、基本設定となる”クラスタの基本”。

f:id:kun432:20210920172308p:plain

”クラスタの基本”では、

  • クラスタ名
  • ロケーション(ゾーン、リージョン)
  • コントロールプレーンのバージョン

が指定できます。コントロールプレーンは、バージョン指定か、Googleのリリースチャンネルで指定できるみたい。

各リリースチャンネルごとのリリーススケジュールやサポートポリシーはこちら。

あと、右にあるクラスタセットアップガイドを使うと、それぞれの用途のおすすめの設定でクラスタ設定がカスタマイズされるみたい。今回は使わないけど。

f:id:kun432:20210920174922j:plain

f:id:kun432:20210920174932p:plain

ノードプール

次にノードプール。左のメニューから"default-pool"をクリックします。

f:id:kun432:20210920175108j:plain

f:id:kun432:20210920175240p:plain

ノードプールは、EKSでいうノードグループと同じもの。デフォルトで、クラスタに必ず1つ作成されます。"default-pool"はデフォルトの名前ってことですね。ここでは、

  • ワーカーノードのノード数
  • 自動スケーリング
  • 自動アップグレード・修復

などが設定できる。"自動アップグレード"、"自動修復"はグレーアウトしてるけど、コントロールプレーンのバージョン指定で"リリースチャンネル"を指定している場合は必須で、"静的バージョン"を指定している場合にOFFにもできるみたい。まあ"静的バージョン"を選択するぐらいなので、自動でアップグレードされたら困るということですね。"自動修復"は基本的にOFFにすることはないだろうけど。

ノード

ノードプールはさらにサブメニューがあります。まず"ノード"。

f:id:kun432:20210920180620j:plain

ここはワーカーノードとなるGoogle Compute Engineのインスタンスの指定ですね。

  • インスタンスイメージの種類
  • インスタンスのマシンタイプ(CPU/メモリ)
  • ディスクサイズ
  • プリエンプティブルノードの有効・無効
  • 1ノードあたりのPod数

あたりが設定できる。Kubernetesとして特に重要そうと思ったのは、"イメージの種類"、"1ノードあたりのPod数"あたりかな。

f:id:kun432:20210920181831p:plain

"イメージの種類"では、

  • cos
    • コンテナランタイムとしてDockerを使う、コンテナ最適化OS
  • cos_containerd
    • コンテナランタイムとして containerd を使う、コンテナ最適化OS。デフォルト。
  • ubuntu
    • コンテナランタイムとして Docker を使う。OSはUbuntu
  • ubuntu_containerd
    • コンテナランタイムとして containerd を使う。OSはUbuntu

あたりが選べるようです。書いてないけどWindowsコンテナも使えるみたい。

"1PodあたりのPod数"は"110"が最大みたいで、これ以上増やすことはできなかった。

f:id:kun432:20210920182917p:plain

オーバーライドできるとあるけど、多分減らすほうにってことかな。EKSだとEC2インスタンスタイプでこのあたりは決まるので、より大きいインスタンスタイプを選択すれば増やすことができるんだけど、マシンタイプ変えても変わらなかった。小さいマシンタイプでたくさんのPodを上げてもしょうがない気もするけど、小さなPodがたくさん必要になるようなケースで大きいマシンタイプを選ばざるを得なくてリソース無駄、ということを考えるとどっちもどっちかなという気がする。

セキュリティ

f:id:kun432:20210920183905p:plain

"セキュリティ"では、サービスアカウントの設定とノードの保護を行えるみたい。ただし、ここで言うサービスアカウントはKubernetesのサービスアカウントじゃなくて、GCPのサービスアカウントっぽい。なのでEKSだとノードとなるインスタンスに割り当てるIAMロールに近いと思う。IAM Roles for Service Accounts みたいなものはたぶんWorkload Identityというやつかな?

メタデータ

f:id:kun432:20210920183914p:plain

"メタデータ"は、その名の通り、メタデータの設定です。ラベルやtaintなどのメタデータを設定できます。

クラスタの詳細設定

ここからはクラスタのより詳細な設定です。この辺はサラッと。

自動化

f:id:kun432:20210920192920p:plain

"自動化"は、アップデートやメンテナンスやスケジュールウインドウを設定したりする感じですね。あとは、スケールアップ・スケールアウトあたり。

ネットワーキング

f:id:kun432:20210920192902p:plain

"ネットワーキング"は、VPC周りの設定など。Node Local DNSCacheの有効化とかもここで出来ちゃうみたい。

セキュリティ

f:id:kun432:20210920192850p:plain

Cloud KMSを使ったsecretの暗号化とか。Workload Identityもここで有効にできるみたいですね。

メタデータ

f:id:kun432:20210920192839p:plain

ここはGCPにおけるメタデータ、つまりAWSのタグと同じようなものだと思う。

特徴量

f:id:kun432:20210920192824p:plain

"特徴量"っていうこの名前、全然ピンとこないけど、主に他の機能と連携するような部分って感じがする。

  • Cloud Logging/Cloud Monitoringなどの有効有無
  • Copute Engine永続ディスクのCSIドライバの有効有無

あたりはデフォルトでオンになってるみたいですね。

クラスタを作成してみる

とりあえず、今回はお試しなので、"クラスタの基本”で、

  • クラスタ名を適当に。今回は"demo-cluster-01"にします。
  • ロケーション。東京リージョンのゾーンのうち一つを選択、今回は、"asia-northeast-1a"を選択。

だけを設定して一番下の「作成」をクリックします。

f:id:kun432:20210920194456j:plain

クラスタ作成中です。ちょっと時間がかかります。

f:id:kun432:20210920194632p:plain

できました。

f:id:kun432:20210920195402p:plain

クラスタへの操作

クラスタへの操作はCloud Shellを使います。

f:id:kun432:20210920212718j:plain

f:id:kun432:20210920195801j:plain

ためしにkubectlを叩いてみます。

$ which kubectl
/usr/bin/kubectl
$ kubectl get node
The connection to the server localhost:8080 was refused - did you specify the right host or port?

ということでCloud Shell側には何も設定されていませんね。クラスタにアクセスするための認証情報を取得します。

$ gcloud container clusters get-credentials demo-cluster-01 --zone asia-northeast1-a

実行すると以下のように承認が求められますので、承認します。

f:id:kun432:20210920212805j:plain

認証情報が取得されて、kubeconfigの設定が行われたようです。

Fetching cluster endpoint and auth data.
kubeconfig entry generated for demo-cluster-01.

ではkubectlを実行してみましょう。

$ kubectl config get-clusters
NAME
gke_sample-e2d18_asia-northeast1-a_demo-cluster-01

$ kubectl get node
NAME                                             STATUS   ROLES    AGE   VERSION
gke-demo-cluster-01-default-pool-aae0737e-cnfl   Ready    <none>   16m   v1.20.9-gke.701
gke-demo-cluster-01-default-pool-aae0737e-jgn2   Ready    <none>   16m   v1.20.9-gke.701
gke-demo-cluster-01-default-pool-aae0737e-qn5p   Ready    <none>   16m   v1.20.9-gke.701

$ kubectl get all --all-namespaces
NAMESPACE     NAME                                                            READY   STATUS    RESTARTS   AGE
kube-system   pod/event-exporter-gke-67986489c8-w2bv2                         2/2     Running   0          17m
kube-system   pod/fluentbit-gke-656xn                                         2/2     Running   0          17m
kube-system   pod/fluentbit-gke-8pvzp                                         2/2     Running   0          17m
kube-system   pod/fluentbit-gke-kxxhs                                         2/2     Running   0          17m
kube-system   pod/gke-metrics-agent-9fkqh                                     1/1     Running   0          17m
kube-system   pod/gke-metrics-agent-9pg8j                                     1/1     Running   0          17m
kube-system   pod/gke-metrics-agent-j5nb8                                     1/1     Running   0          17m
kube-system   pod/kube-dns-autoscaler-844c9d9448-7fl8m                        1/1     Running   0          17m
kube-system   pod/kube-dns-b4f5c58c7-9rkgx                                    4/4     Running   0          17m
kube-system   pod/kube-dns-b4f5c58c7-dk664                                    4/4     Running   0          17m
kube-system   pod/kube-proxy-gke-demo-cluster-01-default-pool-aae0737e-cnfl   1/1     Running   0          17m
kube-system   pod/kube-proxy-gke-demo-cluster-01-default-pool-aae0737e-jgn2   1/1     Running   0          16m
kube-system   pod/kube-proxy-gke-demo-cluster-01-default-pool-aae0737e-qn5p   1/1     Running   0          17m
kube-system   pod/l7-default-backend-56cb9644f6-9rwvr                         1/1     Running   0          17m
kube-system   pod/metrics-server-v0.3.6-9c5bbf784-ntxgs                       2/2     Running   0          16m
kube-system   pod/pdcsi-node-7rg44                                            2/2     Running   0          17m
kube-system   pod/pdcsi-node-8pb8b                                            2/2     Running   0          17m
kube-system   pod/pdcsi-node-qqwtr                                            2/2     Running   0          17m
kube-system   pod/stackdriver-metadata-agent-cluster-level-658b89df-7hzbb     2/2     Running   0          17m

NAMESPACE     NAME                           TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)         AGE
default       service/kubernetes             ClusterIP   10.76.0.1      <none>        443/TCP         18m
kube-system   service/default-http-backend   NodePort    10.76.10.142   <none>        80:31187/TCP    17m
kube-system   service/kube-dns               ClusterIP   10.76.0.10     <none>        53/UDP,53/TCP   17m
kube-system   service/metrics-server         ClusterIP   10.76.10.52    <none>        443/TCP         17m

NAMESPACE     NAME                                       DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR                                                        AGE
kube-system   daemonset.apps/fluentbit-gke               3         3         3       3            3           kubernetes.io/os=linux                                               17m
kube-system   daemonset.apps/gke-metrics-agent           3         3         3       3            3           kubernetes.io/os=linux                                               17m
kube-system   daemonset.apps/gke-metrics-agent-windows   0         0         0       0            0           kubernetes.io/os=windows                                             17m
kube-system   daemonset.apps/kube-proxy                  0         0         0       0            0           kubernetes.io/os=linux,node.kubernetes.io/kube-proxy-ds-ready=true   17m
kube-system   daemonset.apps/metadata-proxy-v0.1         0         0         0       0            0           cloud.google.com/metadata-proxy-ready=true,kubernetes.io/os=linux    17m
kube-system   daemonset.apps/nvidia-gpu-device-plugin    0         0         0       0            0           <none>                                                               17m
kube-system   daemonset.apps/pdcsi-node                  3         3         3       3            3           kubernetes.io/os=linux                                               17m
kube-system   daemonset.apps/pdcsi-node-windows          0         0         0       0            0           kubernetes.io/os=windows                                             17m

NAMESPACE     NAME                                                       READY   UP-TO-DATE   AVAILABLE   AGE
kube-system   deployment.apps/event-exporter-gke                         1/1     1            1           17m
kube-system   deployment.apps/kube-dns                                   2/2     2            2           17m
kube-system   deployment.apps/kube-dns-autoscaler                        1/1     1            1           17m
kube-system   deployment.apps/l7-default-backend                         1/1     1            1           17m
kube-system   deployment.apps/metrics-server-v0.3.6                      1/1     1            1           17m
kube-system   deployment.apps/stackdriver-metadata-agent-cluster-level   1/1     1            1           17m

NAMESPACE     NAME                                                                  DESIRED   CURRENT   READY   AGE
kube-system   replicaset.apps/event-exporter-gke-67986489c8                         1         1         1       17m
kube-system   replicaset.apps/kube-dns-autoscaler-844c9d9448                        1         1         1       17m
kube-system   replicaset.apps/kube-dns-b4f5c58c7                                    2         2         2       17m
kube-system   replicaset.apps/l7-default-backend-56cb9644f6                         1         1         1       17m
kube-system   replicaset.apps/metrics-server-v0.3.6-57bc866888                      0         0         0       17m
kube-system   replicaset.apps/metrics-server-v0.3.6-886d66856                       0         0         0       17m
kube-system   replicaset.apps/metrics-server-v0.3.6-9c5bbf784                       1         1         1       16m
kube-system   replicaset.apps/stackdriver-metadata-agent-cluster-level-55bf669657   0         0         0       17m
kube-system   replicaset.apps/stackdriver-metadata-agent-cluster-level-658b89df     1         1         1       17m

クラスタでnodeが3台、その上でいろいろなpodが動いているのがわかりますね。

podを立ててみる。

ではpodを立ててみます。以下のマニフェストで。

gist.github.com

$ kubectl apply -f https://gist.githubusercontent.com/kun432/e29dd1c14f28806ed3acb1f6c66322c1/raw/e09fcd368634377485d2d9cd92ed14131ba793b3/sample-deployment.yaml
deployment.apps/bootcamp-deployment created

$ kubectl get pod -o wide
NAME                                  READY   STATUS    RESTARTS   AGE     IP          NODE                                             NOMINATED NODE   READINESS GATES
bootcamp-deployment-8d6697479-mpszh   1/1     Running   0          2m11s   10.72.0.6   gke-demo-cluster-01-default-pool-aae0737e-jgn2   <none>           <none>
bootcamp-deployment-8d6697479-n8755   1/1     Running   0          2m12s   10.72.0.5   gke-demo-cluster-01-default-pool-aae0737e-jgn2   <none>           <none>
bootcamp-deployment-8d6697479-rgkbr   1/1     Running   0          2m11s   10.72.1.4   gke-demo-cluster-01-default-pool-aae0737e-cnfl   <none>           <none>

$ kubectl get deployment -o wide
NAME                  READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS   IMAGES                                         SELECTOR
bootcamp-deployment   3/3     3            3           2m41s   bootcamp     gcr.io/google-samples/kubernetes-bootcamp:v1   app=bootcamp

ローカルでアクセスできるか確認してみましょう。gcloudのプレビュー機能とkubectl port-forwardを使うとコンソールからWebアクセスをかんたんに試せます。Cloud Shellの以下のアイコンをクリックするとメニューが表示されます。

f:id:kun432:20210920211814p:plain

デフォルトだと8080で待ち受けてpodに流すようになっているようです。今回使っているkubernetes-bootcampのpodはport8080で待ち受けるようにハードコードされてるのでこうなります。

$ kubectl port-forward deployment/bootcamp-deployment 8080:8080
Forwarding from 127.0.0.1:8080 -> 8080

この状態で、プレビュー機能から"ポート8080でプレビュー"をクリックします。

f:id:kun432:20210920212859j:plain

ブラウザの別タブが立ち上がって、podにアクセスできてるのがわかりますね。

f:id:kun432:20210920212605j:plain

serviceを立ててみる

クラウドにおけるKubernetesマネージドサービスの良さはクラウドの機能と連携できるところですが、特にLoadBalancer Serviceが使えることだと思っています。以下のマニフェストを適用してみます。

gist.github.com

適用してみます。

$ kubectl apply -f https://gist.githubusercontent.com/kun432/d94323661719fc06e13e1e75f7e91848/raw/2698bbdc94b1820efb4c0bc3fbfd8dc4e9a16acb/bootcamp-service.yaml
service/bootcamp-service created

$ kubectl get service
NAME               TYPE           CLUSTER-IP    EXTERNAL-IP   PORT(S)          AGE
bootcamp-service   LoadBalancer   10.76.3.220   <pending>     8888:30080/TCP   6s
kubernetes         ClusterIP      10.76.0.1     <none>        443/TCP          106m

作成したサービスのEXTERNAL-IPが"pending"になっていますが、しばらく待つとこうなります。

$ kubectl get serviceNAME               TYPE           CLUSTER-IP    EXTERNAL-IP      PORT(S)          AGE
bootcamp-service   LoadBalancer   10.76.3.220   XXX.XXX.XXX.XXX   8888:30080/TCP   49s
kubernetes         ClusterIP      10.76.0.1     <none>           443/TCP          107m

XXX...にはグローバルなIPアドレスが付与されていると思います。これにさきほどのserviceのマニフェストにあるspec.ports[].portで指定しているポート8888を付与してブラウザでアクセスしてみましょう。

f:id:kun432:20210920214611j:plain

GCPのリソースはどうなっているかも確認してみましょう。メニューから、"ServicesとIngress"をクリックすると、LoadBalancerが起動してグローバルIPアドレスが割り振られているのがわかると思います。

f:id:kun432:20210920215541j:plain

service名をクリックするとより詳細な情報が見れます。serviceのCluster IPも見えますし、紐付いているdeploymentやpodも見えますね。

f:id:kun432:20210920215821j:plain

まとめ

一通り触ってみましたが、とてもわかりやすいですね。Kubernetesのマネージドサービスは以前はGKE一択と言われてたのも納得できますね。仕事で使うことはなさそうですが、これからKubernetesをはじめる、クラウドでやってみたい、という場合にはGKEのほうがわかりやすいのではないでしょうか。あくまでも個人の印象ですが。

EKS Anywhereでlocal clusterを試す

f:id:kun432:20210912220133p:plain

EKS Anyhereが一般公開されたので試してみました。

目次

環境

以下にドキュメントがあります。

Prerequisiteについては、Install EKS Anywhere | EKS Anywhere にあります。

Administrative machine prerequisites

  • Docker 20.x.x
  • Mac OS (10.15) / Ubuntu (20.04.2 LTS)
  • 4 CPU cores
  • 16GB memory
  • 30GB free disk space

結構スペックが必要。あと、クラスタについては、以下の2種類のようです。

  • local cluster
    • ローカルなシングルクラスタ
    • Dockerがあればよさそう
  • production cluster
    • vShpereが必要

準備

ということで、今回はlocal clusterを使ってみます。手元のMacで作ってもいいんだけど、まずはVagrant上のUbuntuでやってみます。Vagrantfileはこんな感じで。vagrant-disksizeプラグインが必要です。

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/bionic64"
  config.vm.network "private_network", ip: "192.168.33.10"
  config.disksize.size = "100GB"
  config.vm.provider "virtualbox" do |vb|
    vb.gui = false
    vb.cpus = 4
    vb.memory = "8192"
    vb.customize ["modifyvm", :id, "--ioapic", "on"]
  end
  config.vm.provision "shell", inline: <<-SHELL
    apt-get update
    apt-get upgrade -y
  SHELL
end

うちのMBPはメモリが16GBなので、そこだけ減らしました。(32GBほしい・・・)

VMをあげてsshします。

$ vagrant up
$ vagrant ssh

Ubuntuの場合は標準のDockerではなくてDocker CEを使う必要があるようなので、以下に従って入れ替えます。

$ sudo apt-get remove docker docker-engine docker.io containerd runc
$ sudo apt-get update
$ sudo apt-get install -y \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg \
    lsb-release
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
$ echo \
  "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
$ sudo apt-get update
$ sudo apt-get install -y docker-ce docker-ce-cli containerd.io

vagrantユーザでもdockerコマンドが使えるようにして、hello-worldが動くかを確認しましょう。

$ sudo usermod -aG docker $USER
$ newgrp docker
$ docker run hello-world
(snip)

Hello from Docker!
This message shows that your installation appears to be working correctly.

(snip)

dockerがサービスで立ち上がるようにしておきます。

$ sudo systemctl enable docker.service
$ sudo systemctl enable containerd.service

一旦抜けてrebootしておきます。

$ vagrant reload

これで準備完了です。

EKS Anywhereのインストール

ここからは以下の手順に従って実施していきます。

EKS Anywhere CLI toolsのインストール

$ curl "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" \
    --silent --location \
    | tar xz -C /tmp
$ sudo mv /tmp/eksctl /usr/local/bin/

バージョンを確認。

$ eksctl version
0.66.0

EKS Anywhere pluginのインストール

$ export EKSA_RELEASE="0.5.0" OS="$(uname -s | tr A-Z a-z)"
$ curl "https://anywhere-assets.eks.amazonaws.com/releases/eks-a/1/artifacts/eks-a/v${EKSA_RELEASE}/${OS}/eksctl-anywhere-v${EKSA_RELEASE}-${OS}-amd64.tar.gz" \
    --silent --location \
    | tar xz ./eksctl-anywhere
$ sudo mv ./eksctl-anywhere /usr/local/bin/

pluginをインストールすると、以下のようにeksctlからバージョンが確認できるみたい。

$ eksctl anywhere version
v0.5.0

ローカルクラスタの作成

クラスタ作成用のコンフィグを生成します。

$ CLUSTER_NAME=dev-cluster
$ eksctl anywhere generate clusterconfig $CLUSTER_NAME \
   --provider docker > $CLUSTER_NAME.yaml

生成されたコンフィグはこんな感じでした。

apiVersion: anywhere.eks.amazonaws.com/v1alpha1
kind: Cluster
metadata:
  name: dev-cluster
spec:
  clusterNetwork:
    cni: cilium
    pods:
      cidrBlocks:
      - 192.168.0.0/16
    services:
      cidrBlocks:
      - 10.96.0.0/12
  controlPlaneConfiguration:
    count: 1
  datacenterRef:
    kind: DockerDatacenterConfig
    name: dev-cluster
  externalEtcdConfiguration:
    count: 1
  kubernetesVersion: "1.21"
  workerNodeGroupConfigurations:
  - count: 1

---
apiVersion: anywhere.eks.amazonaws.com/v1alpha1
kind: DockerDatacenterConfig
metadata:
  name: dev-cluster
spec: {}

---

podネットワーク(192.168.0.0/16)が、vagrantのプライベートネットワーク(192.168.33.0/24)とかぶっているので、以下だけ変更しておきます。

    pods:
      cidrBlocks:
      - 10.10.0.0/16

ではクラスタを作成します。メモリを減らしたせいかもしれませんが、かなり時間がかかります。

$ eksctl anywhere create cluster -f $CLUSTER_NAME.yaml
Performing setup and validations
Warning: The docker infrastructure provider is meant for local development and testing only
✅ Docker Provider setup is valid
Creating new bootstrap cluster
Installing cluster-api providers on bootstrap cluster
Provider specific setup
Creating new workload cluster
Installing networking on workload cluster
Installing storage class on workload cluster
Installing cluster-api providers on workload cluster
Moving cluster management from bootstrap to workload cluster
Installing EKS-A custom components (CRD and controller) on workload cluster
Creating EKS-A CRDs instances on workload cluster
Installing AddonManager and GitOps Toolkit on workload cluster
GitOps field not specified, bootstrap flux skipped
Writing cluster config file
Deleting bootstrap cluster
🎉 Cluster created!

クラスタが作成されると、クラスタ名のフォルダが作成され、その中にKUBECONFIGが生成されます。

vagrant@ubuntu-bionic:~$ tree
.
├── dev-cluster
│   ├── dev-cluster-eks-a-cluster.kubeconfig
│   └── dev-cluster-eks-a-cluster.yaml
└── dev-cluster.yaml

1 directory, 3 files

KUBECONFIGを読み込みます。

$ export KUBECONFIG=${PWD}/${CLUSTER_NAME}/${CLUSTER_NAME}-eks-a-cluster.kubeconfig

kubectlをインストールします。

$ curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl"
$ chmod +x ./kubectl
$ sudo mv ./kubectl /usr/local/bin/kubectl

kubectlでnodeやpodを見てみます。

$ kubectl get node
NAME                                STATUS   ROLES                  AGE   VERSION
dev-cluster-kl5rn                   Ready    control-plane,master   14m   v1.21.2-eks-1-21-4
dev-cluster-md-0-7c7f759fc6-zc6pc   Ready    <none>                 14m   v1.21.2-eks-1-21-4

$ kubectl get pod --all-namespaces
NAMESPACE                           NAME                                                             READY   STATUS    RESTARTS   AGE
capd-system                         capd-controller-manager-659dd5f8bc-545s2                         2/2     Running   0          13m
capi-kubeadm-bootstrap-system       capi-kubeadm-bootstrap-controller-manager-69889cb844-b8xz7       2/2     Running   0          13m
capi-kubeadm-control-plane-system   capi-kubeadm-control-plane-controller-manager-6ddc66fb75-5l4d8   2/2     Running   0          13m
capi-system                         capi-controller-manager-db59f5789-nnnbm                          2/2     Running   0          13m
capi-webhook-system                 capi-controller-manager-64b8c548db-v8qw4                         2/2     Running   0          13m
capi-webhook-system                 capi-kubeadm-bootstrap-controller-manager-68b8cc9759-g8jcp       2/2     Running   0          13m
capi-webhook-system                 capi-kubeadm-control-plane-controller-manager-7dc88f767d-n6l59   2/2     Running   0          13m
cert-manager                        cert-manager-5f6b885b4-xs6j2                                     1/1     Running   0          15m
cert-manager                        cert-manager-cainjector-bb6d9bcb5-w9h8v                          1/1     Running   0          15m
cert-manager                        cert-manager-webhook-56cbc8f5b8-6sz65                            1/1     Running   0          15m
eksa-system                         eksa-controller-manager-6769764b45-f6q48                         2/2     Running   0          11m
etcdadm-bootstrap-provider-system   etcdadm-bootstrap-provider-controller-manager-54476b7bf9-kh9j4   2/2     Running   0          13m
etcdadm-controller-system           etcdadm-controller-controller-manager-d5795556-rgptd             2/2     Running   0          13m
kube-system                         cilium-d9cfz                                                     1/1     Running   0          15m
kube-system                         cilium-operator-6bf46cc6c6-4hz6x                                 1/1     Running   0          15m
kube-system                         cilium-operator-6bf46cc6c6-6c5np                                 1/1     Running   0          15m
kube-system                         cilium-sxvjh                                                     1/1     Running   0          15m
kube-system                         coredns-7c68f85774-9rgx5                                         1/1     Running   0          15m
kube-system                         coredns-7c68f85774-srzl7                                         1/1     Running   0          15m
kube-system                         kube-apiserver-dev-cluster-kl5rn                                 1/1     Running   0          15m
kube-system                         kube-controller-manager-dev-cluster-kl5rn                        1/1     Running   0          15m
kube-system                         kube-proxy-2fjqz                                                 1/1     Running   0          15m
kube-system                         kube-proxy-kt84t                                                 1/1     Running   0          15m
kube-system                         kube-scheduler-dev-cluster-kl5rn                                 1/1     Running   0          15m

とりあえず動いているようですね。ただ、-o wideつけるとわかるのですが、pods.cidrBlocksを変更(→10.10.0.0/16)したのが反映されていないのかな?ただここを変更しないとクラスタ作成がコケたような気がします(もう覚えてない)

$ kubectl get pod --all-namespaces -o wide
NAMESPACE                           NAME                                                             READY   STATUS    RESTARTS   AGE   IP              NODE                                NOMINATED NODE   READINESS GATES
capd-system                         capd-controller-manager-659dd5f8bc-545s2                         2/2     Running   0          32m   192.168.1.102   dev-cluster-md-0-7c7f759fc6-zc6pc   <none>           <none>
capi-kubeadm-bootstrap-system       capi-kubeadm-bootstrap-controller-manager-69889cb844-b8xz7       2/2     Running   0          32m   192.168.0.140   dev-cluster-kl5rn                   <none>           <none>
capi-kubeadm-control-plane-system   capi-kubeadm-control-plane-controller-manager-6ddc66fb75-5l4d8   2/2     Running   0          32m   192.168.0.11    dev-cluster-kl5rn                   <none>           <none>
capi-system                         capi-controller-manager-db59f5789-nnnbm                          2/2     Running   0          33m   192.168.0.116   dev-cluster-kl5rn                   <none>           <none>
capi-webhook-system                 capi-controller-manager-64b8c548db-v8qw4                         2/2     Running   0          33m   192.168.1.144   dev-cluster-md-0-7c7f759fc6-zc6pc   <none>           <none>
capi-webhook-system                 capi-kubeadm-bootstrap-controller-manager-68b8cc9759-g8jcp       2/2     Running   0          33m   192.168.1.202   dev-cluster-md-0-7c7f759fc6-zc6pc   <none>           <none>
capi-webhook-system                 capi-kubeadm-control-plane-controller-manager-7dc88f767d-n6l59   2/2     Running   0          32m   192.168.0.149   dev-cluster-kl5rn                   <none>           <none>
cert-manager                        cert-manager-5f6b885b4-xs6j2                                     1/1     Running   0          34m   192.168.1.182   dev-cluster-md-0-7c7f759fc6-zc6pc   <none>           <none>
cert-manager                        cert-manager-cainjector-bb6d9bcb5-w9h8v                          1/1     Running   0          34m   192.168.1.150   dev-cluster-md-0-7c7f759fc6-zc6pc   <none>           <none>
cert-manager                        cert-manager-webhook-56cbc8f5b8-6sz65                            1/1     Running   0          34m   192.168.1.199   dev-cluster-md-0-7c7f759fc6-zc6pc   <none>           <none>
eksa-system                         eksa-controller-manager-6769764b45-f6q48                         2/2     Running   0          30m   192.168.0.87    dev-cluster-kl5rn                   <none>           <none>
etcdadm-bootstrap-provider-system   etcdadm-bootstrap-provider-controller-manager-54476b7bf9-kh9j4   2/2     Running   0          32m   192.168.1.64    dev-cluster-md-0-7c7f759fc6-zc6pc   <none>           <none>
etcdadm-controller-system           etcdadm-controller-controller-manager-d5795556-rgptd             2/2     Running   0          32m   192.168.1.82    dev-cluster-md-0-7c7f759fc6-zc6pc   <none>           <none>
kube-system                         cilium-d9cfz                                                     1/1     Running   0          34m   172.18.0.5      dev-cluster-kl5rn                   <none>           <none>
kube-system                         cilium-operator-6bf46cc6c6-4hz6x                                 1/1     Running   0          34m   172.18.0.6      dev-cluster-md-0-7c7f759fc6-zc6pc   <none>           <none>
kube-system                         cilium-operator-6bf46cc6c6-6c5np                                 1/1     Running   0          34m   172.18.0.5      dev-cluster-kl5rn                   <none>           <none>
kube-system                         cilium-sxvjh                                                     1/1     Running   0          34m   172.18.0.6      dev-cluster-md-0-7c7f759fc6-zc6pc   <none>           <none>
kube-system                         coredns-7c68f85774-9rgx5                                         1/1     Running   0          35m   192.168.1.101   dev-cluster-md-0-7c7f759fc6-zc6pc   <none>           <none>
kube-system                         coredns-7c68f85774-srzl7                                         1/1     Running   0          35m   192.168.1.155   dev-cluster-md-0-7c7f759fc6-zc6pc   <none>           <none>
kube-system                         kube-apiserver-dev-cluster-kl5rn                                 1/1     Running   0          35m   172.18.0.5      dev-cluster-kl5rn                   <none>           <none>
kube-system                         kube-controller-manager-dev-cluster-kl5rn                        1/1     Running   0          35m   172.18.0.5      dev-cluster-kl5rn                   <none>           <none>
kube-system                         kube-proxy-2fjqz                                                 1/1     Running   0          35m   172.18.0.5      dev-cluster-kl5rn                   <none>           <none>
kube-system                         kube-proxy-kt84t                                                 1/1     Running   0          34m   172.18.0.6      dev-cluster-md-0-7c7f759fc6-zc6pc   <none>           <none>
kube-system                         kube-scheduler-dev-cluster-kl5rn                                 1/1     Running   0          35m   172.18.0.5      dev-cluster-kl5rn                   <none>           <none>

テスト

以下に従って、テストしてみましょう。

$ kubectl apply -f "https://anywhere.eks.amazonaws.com/manifests/hello-eks-a.yaml"
deployment.apps/hello-eks-a created
service/hello-eks-a create
$ kubectl get pods -l app=hello-eks-a -o wide
NAME                          READY   STATUS    RESTARTS   AGE   IP             NODE                                NOMINATED NODE   READINESS GATES
hello-eks-a-9644dd8dc-bj7l5   1/1     Running   0          20s   192.168.1.86   dev-cluster-md-0-7c7f759fc6-zc6pc   <none>           <none>
$ kubectl port-forward deploy/hello-eks-a 8000:80 &
Forwarding from 127.0.0.1:8000 -> 80
Forwarding from [::1]:8000 -> 80
$ curl localhost:8000
Handling connection for 8000
⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢

Thank you for using

███████╗██╗  ██╗███████╗
██╔════╝██║ ██╔╝██╔════╝
█████╗  █████╔╝ ███████╗
██╔══╝  ██╔═██╗ ╚════██║
███████╗██║  ██╗███████║
╚══════╝╚═╝  ╚═╝╚══════╝

 █████╗ ███╗   ██╗██╗   ██╗██╗    ██╗██╗  ██╗███████╗██████╗ ███████╗
██╔══██╗████╗  ██║╚██╗ ██╔╝██║    ██║██║  ██║██╔════╝██╔══██╗██╔════╝
███████║██╔██╗ ██║ ╚████╔╝ ██║ █╗ ██║███████║█████╗  ██████╔╝█████╗
██╔══██║██║╚██╗██║  ╚██╔╝  ██║███╗██║██╔══██║██╔══╝  ██╔══██╗██╔══╝
██║  ██║██║ ╚████║   ██║   ╚███╔███╔╝██║  ██║███████╗██║  ██║███████╗
╚═╝  ╚═╝╚═╝  ╚═══╝   ╚═╝    ╚══╝╚══╝ ╚═╝  ╚═╝╚══════╝╚═╝  ╚═╝╚══════╝

You have successfully deployed the hello-eks-a pod hello-eks-a-9644dd8dc-bj7l5

For more information check out
https://anywhere.eks.amazonaws.com

⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢⬡⬢

こちらもとりあえず動いているようです。

まとめ

とりあえず動いてはいる、という感じでしょうか・・・

現実的な環境ではないとは予め思いつつも(local clusterの場合)docker-in-dockerっぽいのでネットワーク周り色々ややこしそうだし、しかも今回Vagrantつかったので余計に複雑。Mac上で動かす場合はDocker Desktop+KINDみたいなのでそっちのほうがいくらかマシかもと思いつつ、今回やってみた感じだとクラスタ作成失敗とかするとゴミがたくさん残ったままになってたりして、ちょっとそこは手を出しにくい。

あと、eksctlでやってるようなことができるのかなと思いきや、eksctl anywhereだとできることがほとんどなくて、manifest書き換えてupgradeするしかないのかなーという感じです。

$ eksctl anywhere
Use eksctl anywhere to build your own self-managing cluster on your hardware with the best of Amazon EKS

Usage:
  anywhere [command]

Available Commands:
  create      Create resources
  delete      Delete resources
  generate    Generate resources
  help        Help about any command
  upgrade     Upgrade resources
  version     Get the eksctl anywhere version

local cluster環境だとEKS感はほとんどないし、vsphere使えるならそっちで試したほうがいい気がしますね。

追記)

見るべきところは、EKSっぽさよりもCluster APIってことみたい。

kindでお手軽Kubernetesマルチクラスタを試す②

f:id:kun432:20210901161635p:plain

前回の続き。

kun432.hatenablog.com

目次

ローカルPCからクラスタ内podへのネットワークアクセス(マルチnodeの場合)

extraPortmappingsで、ホストのポートとnodeのポートを紐付けることで、ローカルPCからnode内のpodへのアクセスができます。ただ、ホストポートを使うので、マルチnodeの場合はポートを分けてやる必要があります。クラスタのマニフェストはこんな感じになります。

apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
name: sample
nodes:
- role: control-plane
- role: worker
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30001
    protocol: TCP
- role: worker
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30002
    protocol: TCP
- role: worker
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30003
    protocol: TCP

クラスタを作成します。

$ kind create cluster --config multi-host-ports.yaml

ローカルポート30001〜30003が、それぞれのnodeのポート30000にフォワードされます。

$ kubectl get node
NAME                   STATUS   ROLES                  AGE     VERSION
sample-control-plane   Ready    control-plane,master   3m9s    v1.21.1
sample-worker          Ready    <none>                 2m39s   v1.21.1
sample-worker2         Ready    <none>                 2m39s   v1.21.1
sample-worker3         Ready    <none>                 2m38s   v1.21.1

$ docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED         STATUS         PORTS                       NAMES
a08c381ddc75   kindest/node:v1.21.1   "/usr/local/bin/entr…"   3 minutes ago   Up 3 minutes   0.0.0.0:30003->30000/tcp    sample-worker3
dba747f1183d   kindest/node:v1.21.1   "/usr/local/bin/entr…"   3 minutes ago   Up 3 minutes   127.0.0.1:59931->6443/tcp   sample-control-plane
c8a586bbc81b   kindest/node:v1.21.1   "/usr/local/bin/entr…"   3 minutes ago   Up 3 minutes   0.0.0.0:30002->30000/tcp    sample-worker2
edd676ec1132   kindest/node:v1.21.1   "/usr/local/bin/entr…"   3 minutes ago   Up 3 minutes   0.0.0.0:30001->30000/tcp    sample-worker

nodeportでserviceを立ち上げます。

apiVersion: v1
kind: Pod
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  containers:
  - name: nginx
    image: nginx:1.19.2
    ports:
      - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: my-app
  type: NodePort
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
      nodePort: 30000
$ kubectl apply -f nodeport.yaml

nodeportとして動作はしていますがが、これはちょっとわかりにくいですね・・・

$ curl -s localhost:30001
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
(snip)

$ curl -s localhost:30002
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
(snip)

$ curl -s localhost:30003
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
(snip)

Ingressを使う

ingress-controllerを使う場合もextraPortMappingを使います。公式のドキュメントに従ってみましょう。

クラスタマニフェストはこんな感じです。

apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
name: sample
nodes:
- role: control-plane
- role: worker
  kubeadmConfigPatches:
  - |
    kind: JoinConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"
  extraPortMappings:
  - containerPort: 80
    hostPort: 80
    protocol: TCP

kindはkubeadmを使ってクラスタを作成しますので、kubeadmConfigPatchesを使うとカスタマイズできます。上記ではworkerに対して、kubeadm joinの際に設定を追加して、"ingress-ready=true"というラベルを付けています。(後述するnginx-ingressのサンプルではnodeSelectorでこれを見るようになっています。)

クラスタ作成後にnodeを見てみると、ラベルが付いているのがわかりますね。

$ kubectl get node
NAME                   STATUS   ROLES                  AGE     VERSION
sample-control-plane   Ready    control-plane,master   3m      v1.21.1
sample-worker          Ready    <none>                 2m32s   v1.21.1

$ kubectl get node sample-worker -o json | jq ".metadata.labels"
{
  "beta.kubernetes.io/arch": "amd64",
  "beta.kubernetes.io/os": "linux",
  "ingress-ready": "true",
  "kubernetes.io/arch": "amd64",
  "kubernetes.io/hostname": "sample-worker",
  "kubernetes.io/os": "linux"
}

ではingressです。公式のドキュメントでは以下のingress controllerが紹介されています。

  • Ambassador
  • Contour
  • Ingress NGINX

Ingress NGINX以外はちょっと知らないですね・・・・ということで、Ingress NGINXのサンプルのマニフェストを使いましょう。

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
namespace/ingress-nginx created
serviceaccount/ingress-nginx created
configmap/ingress-nginx-controller created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
service/ingress-nginx-controller-admission created
service/ingress-nginx-controller created
deployment.apps/ingress-nginx-controller created
ingressclass.networking.k8s.io/nginx created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created
serviceaccount/ingress-nginx-admission created
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created

準備ができるまで待ちます。

$ kubectl wait --namespace ingress-nginx \
  --for=condition=ready pod \
  --selector=app.kubernetes.io/component=controller \
  --timeout=90s

ingress-nginx-controllerが起動しました。

$ kubectl get pod -n ingress-nginx
NAME                                        READY   STATUS      RESTARTS   AGE
ingress-nginx-admission-create-jsbpq        0/1     Completed   0          60s
ingress-nginx-admission-patch-sqlcs         0/1     Completed   0          60s
ingress-nginx-controller-6c85cb7b5d-rqn6l   1/1     Running     0          61s

では、pod/service/ingressを設定します。公式のまんまです。

$ kubectl apply -f https://kind.sigs.k8s.io/examples/ingress/usage.yaml
pod/foo-app created
service/foo-service created
pod/bar-app created
service/bar-service created
ingress.networking.k8s.io/example-ingress created

usage.yamlの中身はこんな感じです。/fooと/barで振り分けています。

kind: Pod
apiVersion: v1
metadata:
  name: foo-app
  labels:
    app: foo
spec:
  containers:
  - name: foo-app
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=foo"
---
kind: Service
apiVersion: v1
metadata:
  name: foo-service
spec:
  selector:
    app: foo
  ports:
  # Default port used by the image
  - port: 5678
---
kind: Pod
apiVersion: v1
metadata:
  name: bar-app
  labels:
    app: bar
spec:
  containers:
  - name: bar-app
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=bar"
---
kind: Service
apiVersion: v1
metadata:
  name: bar-service
spec:
  selector:
    app: bar
  ports:
  # Default port used by the image
  - port: 5678
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
spec:
  rules:
  - http:
      paths:
      - pathType: Prefix
        path: "/foo"
        backend:
          service:
            name: foo-service
            port:
              number: 5678
      - pathType: Prefix
        path: "/bar"
        backend:
          service:
            name: bar-service
            port:
              number: 5678

実際にアクセスしてみると、ingressが動作しているのがわかります。

$ curl localhost/foo
foo
$ curl localhost/bar
bar

マルチクラスタでやるならばこうなりますかね。

apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
name: sample
nodes:
- role: control-plane
- role: worker
  kubeadmConfigPatches:
  - |
    kind: JoinConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"
  extraPortMappings:
  - containerPort: 30080
    hostPort: 30001
    protocol: TCP
  - containerPort: 30443
    hostPort: 30011
    protocol: TCP
- role: worker
  kubeadmConfigPatches:
  - |
    kind: JoinConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"
  extraPortMappings:
  - containerPort: 30080
    hostPort: 30002
    protocol: TCP
  - containerPort: 30443
    hostPort: 30012
    protocol: TCP
- role: worker
  kubeadmConfigPatches:
  - |
    kind: JoinConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"
  extraPortMappings:
  - containerPort: 30080
    hostPort: 30003
    protocol: TCP
  - containerPort: 30443
    hostPort: 30013
    protocol: TCP
$ kind create cluster --config multi-node-ingress.yaml

// 以下は公式と同じ
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
$ kubectl wait --namespace ingress-nginx \
  --for=condition=ready pod \
  --selector=app.kubernetes.io/component=controller \
  --timeout=90s

// service: ingress-nginx-controllerのnodeportを変更
$ kubectl -n ingress-nginx patch service ingress-nginx-controller --type='json' -p='[{"op": "replace", "path": "/spec/ports/0/nodePort", "value": 30080}]'
$ kubectl -n ingress-nginx patch service ingress-nginx-controller --type='json' -p='[{"op": "replace", "path": "/spec/ports/1/nodePort", "value": 30443}]'

// ingress/service/podをapply
$ kubectl apply -f https://kind.sigs.k8s.io/examples/ingress/usage.yaml
$ for i in 1 2 3; do curl http://localhost:3000${i}/foo; done
foo
foo
foo
$ for i in 1 2 3; do curl http://localhost:3000${i}/bar; done
bar
bar
bar
$ for i in 1 2 3; do curl -k https://localhost:3001${i}/foo; done
foo
foo
foo
$ for i in 1 2 3; do curl -k https://localhost:3001${i}/bar; done
bar
bar
bar

想定どおりですね。

まとめ

ネットワーク周りには制約がありますが、ローカルでマルチnodeの確認ができるのは良いですね。extraPortMappingsあたりの設定はクラスタ作成時にやってしまう必要があり(あとから設定変更する方法はわかっていない)、場合によってはクラスタ作り直しの必要があるのですが、サクッとできてしまうのでそれほど苦にならない気がしました。