【備忘録】kindでKubernetesに触ってみよう③

前回のあらすじ

ついにnginxを起動してブラウザからアクセスすることに成功しました。

nginxを走らせるPodを立ち上げ、そのPodの80番ポートをServiceのNodePortを使ってホストマシンの特定のポートに公開することで動いています。

マニフェストはこんな書き方もできるよ

前回のservice.yamlnginx.yamlを例にとると

# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: hello-service
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
    - port: 80
      nodePort: 30001
      protocol: TCP
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  containers:
    - name: nginx
      image: nginx:latest

このように、---で区切れば一つのファイルで複数のリソースを記述できます。 これをapplyすると

$ kubectl apply -f=service.yaml
service/hello-service created
pod/nginx created

こんな感じになって、二つ作れていることがわかります。今後は積極的にこういう書き方をしていきます。

ReplicaSetとDeployment

前回、「ReplicaSetはPodを冗長化して管理するもの、DeploymentはReplicaSetを管理するもの」といいました。もうちょっとマシな説明をしていきます。

ReplicaSet

名前から明らかですが、ReplicaSetの本質はレプリケーションです。
ReplicaSetは指定したPodのレプリカを指定した数だけ作ってくれるリソースです。ただ作るだけでなく、作った後はレプリカ数を一定に保つように監視していて、Podが死んだら回復させてくれます(オートヒーリング)。ちなみに、Nodeが死んだ場合にも、別のNode上でPodを走らせることで、トータルのレプリカ数を維持します。 ReplicaSetのおかげで、指定したレプリカ数だけPodが動いていることが保証されるわけですね。

昔はReplicationControllerという名前だったらしいです。公式ドキュメントを見るとdeprecatedの匂いが充満していますね。Kubernetesに歴史あり。

Deployment

DeploymentはReplicaSetを管理しますが、ReplicasetとPodの関係とはまた違います。
マサカリを恐れずに言うと、DeploymentはReplicaSetのアップデートを管理する機能です。もっというと、"ローリング"アップデートを上手いことやってくれます。
通常のアップデート(Red/Black Deployを想定)はアップデートしたいReplicaSetのバージョンを一気に全部切り替えるのに対し、ローリングアップデートでは、古いバージョンのReplicaSet(旧RS)から新しいバージョンのReplicaSet(新RS)へ、少しずつ移行していきます(アップデート完了時に旧RSのレプリカ数は0になります)。

例えば、(Deploymentを使わずに)ReplicaSetを作ったとしましょう。少し時間が経ち、アプリケーションの新しいバージョンができたので、ReplicaSetをアップデートすることになりました。ここで、新RSを作ってServiceの実行を新RSに全振りすると問題が発生したときにまずいことになります。すなわち、不幸にも本番環境でエラーが起きた場合には、サービスが完全にダウンすることになります。
一方、リクエストを少しずつ新RSに流していくようにした場合はどうでしょう。こちらでは、新RSでサービスが崩壊したとしても旧RSは生きているので、アップデートの影響でサービス全体が完全に停止することはありません。"アップデートのためのメンテナンス"というダウンタイムも消えます。
また、新RSが使い物にならないことがわかるとアップデートは即刻中止されますが、この時にすることは"ロールバック"ですよね。実はこちらもDeploymentの仕事のうちであり、Kubernetesの機能を使って旧RS時代へ戻すことができます。

PodとReplicaSetとDeployment、どれを使えばいいのか

先ほどまでの説明から、DeploymentはReplicaSetを管理していて、ReplicaSetはPodを管理しているといえます。よって、我々は常に最上位のオブジェクトであるDeploymentを使えばOKです。例外はきっとあるけど!
前回も言いましたが、公式ドキュメントでも大抵の場合Deploymentを使うことが推奨されています

Cluster, Deployment, Serviceの立ち上げ

Clusterのマニフェストはこちら。

# cluster.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: hpa
nodes: 
  - role: control-plane
  - role: worker
    extraPortMappings:
    - containerPort: 30001
      hostPort: 8000

前回と名前が違うだけですが一応載せておきました。

続いて、Deployment, Serviceのマニフェストがこちら。

# deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hpa-deploy
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        resources:
          limits:
            cpu: 500m
          requests:
            cpu: 200m
---
apiVersion: v1
kind: Service
metadata:
  name: hpa-svc
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
    - port: 80
      nodePort: 30001

長い記述ですが分解していけば何をやっているかは簡単です(難しいとぼくもわからない)。

とりあえずNodePortについては名前以外前回と同じであることがわかると思います。

Deploymentについてですが、templete:以下を見ると、一旦resources:という新キャラを見なかったことにすると、前回のPodのマニフェストで全く同じ記述がありましたね。templeteレプリケーションしたいPodのspecificationを書いている場所になります。

resources:ですが、ここにはPodが要求する計算資源の量が書いてあります。CPUの場合、1000mを指定すればCPU1コア分の計算資源が割り当てられます。ちなみに、1m単位で指定できます。今回は要求量が200mで上限が500mということになります。メモリも指定できますが割愛!

次に、ネストが浅い方のspec:以下に注目すると、replicas: 1templete:を従えていることから、これはReplicaSetのspecificationであることがなんとなくわかります。Deploymentのspecificationを記述しているspec:がないのは、Deploymentはアップデート時の挙動以外はReplicaSetと等価なので、ReplicaSetのspecがあれば十分だからですね(実は等価じゃないかもしれんけどちょっと触った程度の僕には、アップデート以外の部分でのReplicaSetとDeploymentの違いはわかりませんでした。ごめんね!)。

以上から、このDeploymentはnginxを積んだコンテナ1個(レプリカ数が1だから)を起動して、そいつの80番ポートとホストマシンの8000番ポートを繋げることを意味していることがわかります。

$ kind create cluster --config=cluster.yaml
$ kubectl apply -f deploy.yaml

で早速起動しましょう。

Horizontal Pod Autoscaler(HPA)でオートスケーリングをしてみよう

HPAとは

HPAはDeployment、ReplicaSet、StatefulSet(まだ未登場)といった、k8sレプリケーションしてくれるやつのレプリカ数を、CPU使用率などのメトリクスを使って自動で増減してくれるリソースです。水平にスケールするからHorizontal。

Horizontalがあるなら当然Vertical Pod Autoscalerもある(自動でPodに割り当てる計算資源を変えてくれる)のですがこちらはあんまり使われていないような気がします。

HPAが使うメトリクスですが、Kubernetesが自動でいい感じに監視してくれていると思いきや、そうでもないです。k8sではMetrics APIを通してコンテナのCPU使用率などを見られるのですが、このAPIはプラグインによって実装されるらしく、公式ではメトリクスサーバがないとMetrics APIは利用できないと明記されています。

というわけでクラスタにメトリクスサーバを立てましょう。

Metrics Serverを立てよう

Metrics Serverは、コンテナを監視しているkubeletのSummary APIから統計情報を得て、それをkube-api-serverからアクセスできるようにしてくれる人です。この人をクラスタに召喚すればMetrics APIが使えるようになり、kubectl top nodesとかのtop系が動くようになります。

ちなみに、Metrics Serverはメトリクスの精度が重要な場合には適していなくて、あくまでHPA(とVPA)のために使うものだそうです。

さて、リポジトリのREADMEのInstallationに従い、以下のコマンドを実行しましょう。

$ kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

serviceaccount/metrics-server created
clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader created
clusterrole.rbac.authorization.k8s.io/system:metrics-server created
rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader created
clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator created
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-server created
service/metrics-server created
deployment.apps/metrics-server created
apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io created

これで、クラスタにMetrics Serverをインストールできました

……と思いきや、kubectl top nodesはまだ動きません。

$ kubectl top nodes

Error from server (ServiceUnavailable): the server is currently unable to handle the request (get nodes.metrics.k8s.io)

Metrics ServerのPodを確認してみると、

$ kubectl get po -A
NAMESPACE            NAME                                        READY   STATUS    RESTARTS   AGE
...
kube-system          metrics-server-847dcc659d-mrldn             0/1     Running   0          96s
...

Metrics ServerのPodが一向にReadyにならないことがわかります。

調べてみたところ、 kindのIssueのコメントに解決策がありました。

kubectl edit deploy metrics-server -n kube-system

で、argsの部分に、- --kubelet-insecure-tlsを追加すると、

kubectl get po -A
NAMESPACE            NAME                                        READY   STATUS    RESTARTS   AGE
...
kube-system          metrics-server-5c85b4d56f-8m5bk             1/1     Running   0          56s
...

無事Readyになりました。topを動かしてみると

kubectl top nodes
NAME                CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
hpa-control-plane   629m         7%     710Mi           2%
hpa-worker          130m         1%     225Mi           0%

OK!

ちなみに、minikubeならアドオン入れるだけでOKという簡単仕様らしいので、Metrics Serverが必要ならminikubeでやってもいいかも。

HPAリソースの立ち上げ

HPAリソースもマニフェストを書いて立ち上げることはできるのですが、公式のQuick Start的なやつにコマンドで立ち上げるやつが載っているので、今回はそれを使います。というわけで以下を実行。

$ kubectl autoscale deployment hpa-deploy --cpu-percent=50 --min=1 --max=10

horizontalpodautoscaler.autoscaling/hpa-deploy autoscaled

さて、ちゃんと作ったdeploymentがhpaできるようになっているかを確認しましょう。

$ kubectl get hpa
NAME         REFERENCE               TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
hpa-deploy   Deployment/hpa-deploy   <unknown>/50%   1         10        1          26s

作ってすぐはメトリクスの情報が届いてないので、unknownになります。 しばらくすると、

$ kubectl get hpa
NAME         REFERENCE               TARGETS    MINPODS   MAXPODS   REPLICAS   AGE
hpa-deploy   Deployment/hpa-deploy   0%/50%    1         10        1          18m

こんな感じで、リソースの使用率が見えます。AGEの値が無駄にデカいですが、別に18分待たないといけないわけではなく、デフォルトでは30秒間隔でメトリクスを取得するらしいです。だからといって30秒待てば動くわけではないですが。

ServiceにDoSアタックを仕掛けよう

HPAが立ち上がったので、DoSアタックでCPU使用率を爆上げさせれば、レプリカ数が増えることが期待されます!というわけでabコマンドでDoSアタックを仕掛けましょう! デフォルトでは入ってないので、お持ちでない方は次のコマンドをどうぞ。

$ sudo apt install apache2-utils

それでは、次のコマンドでDoSアタックが始まります。nやcはPCのスペックに合わせて適当に弄ってください.

$ ab -n 100000 -c 10 http://localhost:8000/

30秒ほど待ってHPAリソースの様子を見てみると

$ kubectl get hpa
NAME         REFERENCE               TARGETS    MINPODS   MAXPODS   REPLICAS   AGE
hpa-deploy   Deployment/hpa-deploy   207%/50%   1         10        1          48m

CPU使用率が爆上がりしています。 続けて、もうちょっとだけ待ってから再び見てみると

$ kubectl get hpa
NAME         REFERENCE               TARGETS    MINPODS   MAXPODS   REPLICAS   AGE
hpa-deploy   Deployment/hpa-deploy   207%/50%   1         10        4          48m

レプリカ数が増えてますね!オートスケールができていることがわかります。ちなみに、最終的にレプリカ数は6まで増えました。

さて、レプリカ数が減らないうちに、もう一度同じ条件でabコマンドを実行して、処理速度の違いを確認してみましょう。

1回目(最初はレプリカ数1)の結果は以下

$ ab -n 100000 -c 10 http://localhost:8000/
...
Time taken for tests:   61.301 seconds
...
Requests per second:    1631.30 [#/sec] (mean)
...

2回目(最初からレプリカ数6)の結果は以下

$ ab -n 100000 -c 10 http://localhost:8000/
...
Time taken for tests:   46.180 seconds
...
Requests per second:    2165.45 [#/sec] (mean)
...

トータルでかかった時間が15秒減り、1秒当たりに捌くリクエストが500件ほど増えていますね!HPAの力は偉大💪

お気持ち

ReplicaSetのユースケースに、アップデートされないReplicaSetはDeploymentによる管理が不要みたいに書いてたけど、別にDeploymentで管理してもいいと思う。
となると、ReplicaSetはユーザ独自の戦略でアップデートしたいときのみ、直接使われるということになるはず。しかし、外部のプラグインを使うとカナリーリリースとかもできるらしいので、結局ReplicaSetが直接使われることはないんじゃないか。

お片付け

$ kind delete cluster --name=hpa

次回予告

  • Persistent VolumeとかStatefulSetをやる
  • fluentdとか立ち上げてDaemonSetも一緒にやるかも?

これにておしまい。

【備忘録】kindでKubernetesに触ってみよう②

前回のあらすじ

$ kind create cluster

で無のクラスタができました。

$ kubectl cluster-info

でcontrol planeのAPI server(後述)のポートを見つけて、無謀にもcurlでリクエストを出し、無事門前払いされました。

クラスタ生成時に自動生成されるPodを眺めてみよう

$ kind create cluster

を行ったとき、バックヤードではKubernetesクラスタの司令塔であるところのコントロールプレーンが作成されています。コントロールプレーンがないとクラスタは動きません。 ちなみにクラウド環境だとコントロールプレーンはプロバイダが提供してくれるらしいです。
コントロールプレーンには、クラスタを管理するための4つのPodが積まれています。次のコマンドで確認可能です。

$ kubectl get pods --all-namespaces | grep kind-control-plane

結果は以下

kube-system          etcd-kind-control-plane                      1/1     Running   0          9m57s
kube-system          kube-apiserver-kind-control-plane            1/1     Running   0          9m59s
kube-system          kube-controller-manager-kind-control-plane   1/1     Running   0          9m57s
kube-system          kube-scheduler-kind-control-plane            1/1     Running   0          9m57s

etcd、kube-apiserver、kube-controller-manager、kube-schedulerの4つですね、
etcdはKey Value Storeであり、クラスタに関する情報が保存されています。
kube-apiserverは、kubectl applyとかのクラスタをいじるためのKubernetesAPIを外部に公開している人です。kubectlコマンドは全てここへと通じます。認証されてないユーザがクラスタをいじれるとまずいのでここにブラウザで殴り込みをかけると当然ながら、無慈悲な弾かれが発生します(1敗)。RBACという方式でアクセス制御しているらしいです。
kube-controller-managerは、Nodeが死んだときの対応や、Podが死んだときのオートヒーリングの実行など、多くの役割があります。
kube-schedulerは、Podを走らせるNodeを決める役割があります。決め方はリソース要求量とか、各種制約とか、いろいろな指標が考慮されるようです。
といった話は全てこちらの公式ドキュメントに書いてありますので、一読してみてください。別にエキスパートでもなんでもない謎の人間のブログより公式ドキュメント、これ鉄則。

さて、この記事の存在価値を全否定したところで、今回はNginxを立ち上げてトップページを確認するところまで行きます。

Nginxを立ち上げよう

の前に

Pod、Service、Nodeに関して言及したいと思います。

PodはKubernetesで動かしたいプロセス(大抵はアプリケーション)を実際に実行してくれるやつです。つまり、こいつがすべての基本になります。 その中身は1つ以上のコンテナであり、メインのコンテナと、それを補佐するサブのコンテナ(EnvoyやIstioみたいなプロキシとか、fluentdみたいなログエージェントとか)から成ります。サブのコンテナをサイドカーと言います。

Serviceは、公式ドキュメントによると

Podの集合で実行されているアプリケーションをネットワークサービスとして公開する抽象的な方法です。

らしいです。
こいつはアプリケーションをクラスタの外に公開したり、クラスタ内でアプリケーション同士が協調するためのお膳立てをしたりしてくれます。すなわちアプリケーションにエンドポイント(仮想IP)を与えて通信を成立させるわけですね。しかもそのエンドポイントはクラスタ内で勝手に動いているDNSに登録されるようになっています。kubectl cluster-infoで見える謎のDNSくんはServiceのためのコンポーネントだったんですね。
クラウド環境だとクラウドプロバイダが提供するロードバランサをServiceとして使えるらしいですが、手元で遊んでるだけの僕は関係ないのでパスします。MetalLB?アハハ!

Nodeですがこれは一番簡単で、Podが走るマシンのことです。仮想マシンでも物理マシンでもどっちでもOKですが、今回のケースでは仮想マシンですね。
ところで、ここを逃すとおそらく言及する機会がないので言及しますが、実はすべてのノード(マスターノード、ワーカーノード)上で動いている人たちがいます、kubeletはコンテナが動いているか監視してくれます。kube-proxyはNode内のネットワークをいい感じにしてくれて、この人のおかげでPodへの(クラスタ内/外両方からの)アクセスが実現している(=Serviceという抽象的な機能の一部を担当している)ようです。
コンテナの実行を担当するコンテナランタイムくんはちょっと前に話題になりましたね。Kubernetes 1.20からコンテナランタイムにDockerを使うのが非推奨に、1.24に至っては完全に使えなくなりました。現在は主にcontainerdが使われていまして、それを確認することもできます。適当にクラスタを立ち上げて

kubectl describe node | grep Container

を打つと

    Container Runtime Version:  containerd://1.5.10

このとおりわかります。

クラスタの設定

kindではマルチノードなクラスタを立てることができます。せっかくなので、マスターノードとワーカーノードを別で立ててみましょう。 クラスタマニフェストは以下のようになります。

# cluster.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: hello-world #クラスタ名
nodes:
  - role: control-plane
  - role: worker
    extraPortMappings:
    - containerPort: 30001 #ServiceでNodePortとして開放するポート
      hostPort: 8000 #ホスト向けに公開するポート
      protocol: TCP

extraPortMappingsに関して、containerPort/hostPortはServiceとホストを繋げるための設定です。このケースでは30001番で動いているServiceをホストの8000番につなげます。 apiVersionは何も考えず写経しました。こいつだけは何をもって決めるのか一切わかりません。あとは見たまんまですね。nodesのroleを増やせばNodeが増えます。

kind create cluster -f cluster.yaml

クラスタを立ち上げられます。この時点では、このクラスタは無です(まぁコントロールプレーンには4つのPodがありますが……)。

Podの設定

今回のようにちょろっとNginxを走らせたいというだけであれば、Podの中身はnginxのコンテナ1つだけで事足ります(ただし、例えばログをきっちり管理したい場合、ログ基盤に転送するためのサイドカーをつけたりするはず)。 というわけでPodのマニフェストは以下のようになります。

# nginx.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx #Pod名
  labels:
    app: nginx #key,valueともに何でもよい。Serviceのselectorフィールドで再び登場する。
spec:
  containers:
    - name: nginx
      image: nginx:latest

ラベルは、Kubernetesのオブジェクトを選択するために使います。今回はServiceが公開するPod(∈オブジェクト)を選択するのに使います。本番環境用のPodを選択したい場合、env:prdみたいなキーをつけると選択するときに便利そうですね。まぁ実務経験ないから知らんけど。
specにはPodの中身、つまりコンテナ(と永続化が必要な場合はボリューム)の情報が記述されます。
というわけで、上述のマニフェストを使ってnginx用のPodをワーカーノードに生やしてあげましょう。そのためのコマンドがkubectl applyです。

$ kubectl apply -f nginx.yaml
pod/nginx created

これでnginxが走るPodを起動できました。最後に、このPodをクラスタの外部(=ホストの8000番)に公開するための設定をすればクリアです。 (ちなみに、PodをPod単体で起動するとオートヒーリングが効かないので、次回紹介するReplicaSetやDeployment、というか大抵Deploymentのみを使うことが推奨されます。ざっくりいうとReplicaSetはPodを冗長化して管理するもの、DeploymentはReplicaSetを管理するものです。)

Serviceの設定

Serviceのマニフェストはこちらです。

#service.yaml
apiVersion: v1
kind: Service
metadata:
  name: hello-service #service名
spec:
  type: NodePort
  selector:
    app: nginx # 公開したいpodのラベル(pod用のmanifestで設定したlabelのKeyとValueが一致するもの)
  ports:
    - port: 80 #内部で使うポート
      nodePort: 30001 #クラスタの外からアクセスするときに使うポート
      protocol: TCP

ここでselectorとしてさっきのPodにつけたラベルのapp:nginxが出てきましたね。これはapp:nginxのラベルがついているPod全体の集合を並列に展開されたサービスとみなしてその窓口になる仮想IPを作って負荷分散するというものでしょう(TODO:これが嘘か本当か調べる)。これ全然違うPodでラベルが重複したらヤバいことになりそうですね。
portに関しては、ここでは省略しているフィールドが関連していて、説明しづらいので一旦パスです。
ところで、僕はこの記事を書いていてnodePortに関して強烈な違和感を覚えました。
クラスタの外からアクセスするための仕組みがそもそもあるなら、クラスタマニフェストで謎のマッピングなんかせずにこれを使ってアクセスすればいいじゃないですか。
NodePortの使い方は<任意のNodeのIP>:<NodePort>って書いてました。どのNodeに出してもクラスタ内で適切に回してくれるみたいですね。その代わり、異なる二つのNodeで同じポート番号を別の用途に使おう!ということはできないようです。それができる嬉しさは特に思いつかないですが。 さて、NodeのIPを確認するには

kubectl describe node

でOKです。結果は

...
Addresses:
  InternalIP:  172.21.0.2
  Hostname:    hello-world-worker
...

_人人人人人人人人人人人_
> Internal IPしかない <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^ ̄
Internal IPしかないということはNodeが外部に公開されていないということで、つまりNodePortによるクラスタ外からのアクセスは不可能ということです。 怒りのあまりk8sリポジトリにissueを2億個ぐらい立ててやろうかと思いましたが、すんでのところでNodeに関する公式ドキュメントを読みました。

これらのフィールドの使い方は、お使いのクラウドプロバイダーやベアメタルの設定内容によって異なります。

ウー

追記1 kindのLoadBalancerのページ

With Docker on Linux, you can send traffic directly to the loadbalancer's external IP if the IP space is within the docker IP space.
On macOS and Windows, docker does not expose the docker network to the host. Because of this limitation, containers (including kind nodes) are only reachable from the host via port-forwards, ...(以下略)

って書いてました。特定OS上のDockerの挙動からくる問題みたいですね。WindowsMacを使うときは覚えておくといいかも。

ドンマイ。
気を取り直してserviceを起動しましょう。

$ kubectl apply -f service.yaml
service/hello-service created

これでやることは全て終わったので、localhost:8000へアクセスすると。Nginxのいつものアレが見られるはずです。 ダメだった場合、コピペをミスったか、この記事が古すぎてKubernetesが別時空の存在になってしまったか、環境構築が失敗していると思います。 とりあえずkubectl get podskubectl get svckubectl get eventで怪しげなエラーが起きていないか見てみましょう。

お気持ち

これホストにNodePort繋げてるNodeが沈んだら終わりじゃね?

お片付け

kind delete cluster --name=hello-world

次回予告

  • ReplicaSetとDeploymentやる
  • HPAもやる

これにておしまい。

【備忘録】kindでKubernetesに触ってみよう①

最近、一年も積んでいた「Kubernetes完全ガイド 第二版」をようやく読み始めました。
そんなわけで、この本を読んでKubernetesを完全に理解したいと思い、とりあえず手を動かすことを決意しました。 以下、(バージョンによる違いがなければ)手順と結果の再現性はあると思いますが、解説的なアレはないです。知識がないからね。備忘録備忘録。

使用したOSするWindows 10です。 WinでローカルにKubernetesを立てるなら、minikubeかDocker Desktop for Windowsの付属のアレかkindか 自力 ですが、今回はkindを使うことにします。(minikubeは以前ハンズオンで触ったことがあるし、Dockerの付属のやつはできることしょぼいらしいので)

インストール

今回インストールするもの

インストール手順

cd your_workspace

mkdir -p tmp/bin 

curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl"

curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.12.0/kind-linux-amd64

mv ./kind ./kubectl tmp/bin

export PATH=$(pwd)+"/tmp/bin:$PATH"

DockerとWSL2は、ググってよしなにやってください(こんなの見てる時点でこの2つは既に入れてるだろうと思っている)
また、Docker Desktop for Windowsを入れたら勝手にWSL2でkubectlが入ってきますが、こいつは使いません。

$ kubectl version --client
$ kind version

を実行して

$ kubectl version --client
Client Version: version.Info{Major:"1", Minor:"23", GitVersion:"v1.23.6", GitCommit:"ad3338546da947756e8a88aa6822e9c11e7eac22", GitTreeState:"clean", BuildDate:"2022-04-14T08:49:13Z", GoVersion:"go1.17.9", Compiler:"gc", Platform:"linux/amd64"}

$ kind version
kind v0.12.0 go1.17.8 linux/amd64

ちゃんとバージョンが出てきたらとりあえずOKです(バージョンを気にする必要があるようなことは当面の間しないはず!)。

Kubernetesクラスタの立ち上げ

$ kind create cluster

を実行して、しばらく待つと

Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.23.4) 
 ✓ Preparing nodes 
 ✓ Writing configuration 
 ✓ Starting control-plane 
 ✓ Installing CNI 
 ✓ Installing StorageClass 
Set kubectl context to "kind-kind"
You can now use your cluster with:

kubectl cluster-info --context kind-kind

Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 

このとおり、立ち上がります。 特にDockerイメージを指定していなければ、デフォルトでkindest/nodeというイメージを使ったノード一つからなるクラスタが立ち上がります。
僕は、とりあえずcurlで叩いてみる人間なので、

$ kubectl cluster-info

で、ポート(多分ランダム)を確認しまして、

Kubernetes control plane is running at https://127.0.0.1:44921
CoreDNS is running at https://127.0.0.1:44921/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

アクセスをすると……!

$ curl localhost:44921
Client sent an HTTP request to an HTTPS server.

グエー

どうやら、現状このクラスタは外部からのアクセスを受け付けるものではないようで、httpsに直したリクエストを出しても認証できなくて死ぬ。おとなしくNginxでも立ち上げてくださいという感じなのかな?

とはいえ、動いていることは確認できたので、お片付け。

kind delete cluster

これにておしまい。

SNS作った

はじめの前に

こちらは鉄球連盟アドベントカレンダーの15日目の記事です。

要約

  • ゆかりおんさんと一緒にSNSつくりました
    • デザインの洗練は全くやってないです、JSはゼロ、CSSはねこの額ぐらい
  • OpenAPI使ってみました、便利ですね
  • デザインから逃げてはダメ
  • Redis使ってみましたが、激浅知識で破滅しました
  • アーキテクチャは真面目にやりましょう

はじめに

Haiku、書きたくないですか?
そんなニーズにお応えするべく、HaiqueというSNSをね、立ち上げました。

嘘です。SNSっぽいの作ってみたかっただけです。

サービスの仕様

Haiqueでは、Haikuが書けます。Haikuとは一般的な俳句とは全く関係のない、ただ三つの節に分かれているだけの短文です。
また、ユーザをSubscribeでき、Subscribeしてからのユーザのポストは自身のタイムラインに載ります。Unsubscribeすると載らなくなりますが、過去にタイムラインに載っていたHaikuは残ります。自分はSubscribeもUnsubscribeもできず、自分のポストは勝手に載ります。

開発

構成

Haiqueはこんな感じの構成となっております

味わいのあるサムネが欲しかったので、手書きにしました。

なぜかRedisをレプリケーションもなしにメインDBとして使用しています。Redisは今回の要件にマッチしていないので、これを真似するのはやめましょう。Redisは「最悪消えても大丈夫なデータ」を持つべきだと思います。やりたいことに合ったデータ構造を使えとどこかの誰かが言っていましたが、データベースでも同じです。

ところで、BFFはあまり知られていないのではないでしょうか。BFFとはBackends For Frontendの略で、通常はマイクロサービスにおいて、複数のAPIが違うサービスとして動いているときにフロントエンド側から見たエンドポイントを統一するために使うやつです。 しかし、HaiqueではBFF君はフロントエンドの代わりに単一のAPIを叩き、引っ張ってきたデータからHTMLを生成して渡す役割を担っています。そもそもマイクロサービスでも何でもないのにBFFって何?
今回、CSRSSRで迷いました。すなわち、APIを叩くのがフロントエンドなのかバックエンドなのかという問題なのですが、フロントエンドの練度が浅く、なるべくバックエンドに注力したかったので、SSRにしました。
のちのちAPIサーバやDBを(無駄に)並列に走らせようと考えているのですが、その点でもこちらの方が便利だと思います(実はこの思惑こそがうちのBFFくんがあわれにもBFFという名前になってしまった理由です、たとえ並列化しても同一のサービスだからBFF置く意味多分ないけど)。

分担

実装の分担ですが、僕の担当が

  • OpenAPIによるAPI定義
  • 開発用のコンテナ群を作成
  • DBにぶち込むKeyのラベルとValueの意味&型の定義
  • APIの大部分
  • BFFの半分ぐらい

で、ゆかりおんさんの担当が

  • APIのエンドポイント一部
  • BFFの半分くらい
  • デプロイ周りすべて

でした。これだけ見ると大体僕が作ったように見えますが、Azureへのデプロイは地獄の様相を呈しており、労力でいうと6:4ぐらいだと思います。つらさでいうと1:9ですかね。

OpenAPIを使っての感想

OpenAPIを使って一番感動したのは、勝手にスタブが生えるところです。今回はStoplight Studioを使っており(Stoplight Studioの詳細は全く説明しません)何もしなくてもテスト用のエンドポイントが立っているじゃないですか。これでフロントエンドとAPIをコストをかけずに並列に進められるのは極めて優れていると思います。まぁこれはgRPCでも同じことが言えて、結局、やりとりするデータを定義しているのは強いという話ですね。

失敗1

前述のとおり、本来ならばフロントエンドとAPIを並行して実装できるのですが、フロントエンドをガン無視してAPIから作っていきました。最終的な画面の構成を全く考えずに

Redisの採用から若干その気配を感じ取っている読者もおられると思いますが、かなり衝動的に開発していたため、デザインとかフロントエンドとか、そういうのは全く固めずに進んでいました。

デザインが固まってないのはかなり致命的で、画面を決めないと欲しい情報がまとまりません。その結果、衝動的に決めたAPI定義は何度も書き直しになり、かなりめんどくさかったです。体感的には実際の開発でも(最初のうちは)API定義の不備による書き直しがありそうですが、それにしても尋常じゃないペースで書き直しが発生していました。

たとえどんなにバックエンドが書きたくとも、デザインから決めていきましょう。マジで。

Redisを使っての感想

今回、メインDBとしてRedisを使った理由は二つあって、一つは使ってみたかったからです。もう一つは、Redis公式による簡易版Twitterの実装例があるので、詰んでも最悪なんとかなるだろうと思ったからです。タイムラインは最初めちゃくちゃ重い実装だったのですが、公式のを参考に改善しました。
でも、真面目にやるならRDBを用意した方がいいです。本当に。
まともなRedisの使い方をしたのはセッションの保持だけですね。セッションは「最悪消えてもいいデータ」です。ユーザにもう一回ログインしてもらえばなんとかなりますので。

まぁ、売り物でもないので、全部「消えてもいいデータ」なんですが……

Redisの最大の旨味は速度だと思いますが、その辺はサービスの規模が小さいのでよくわかりませんでした。多分、経験値としてはHello worldぐらいのもんです。 ただ、KVSだけでも力を込めれば一応いろいろ実装できるというビジョンは見えました(Redisのデータ型が豊富なおかげ?)。もちろんチューニングとか大規模なデータの場合とかは全然考えていませんが。

あとは、SPAだったらWebsocket使ってRedisお得意(?)のPub/Subが使えたかもしれないですね。その点についてはあんまり後悔はしていないですが。

失敗2

RedisにはHash型というのがありまして、これは他でいうところの連想配列です。 Hash型を使うとRedis上で構造体をうまく表現できるのですが、僕はAPIが完成するまでこれを知りませんでした。よって、Haiqueでは使われていません。完全に勉強不足です。悲しすぎる。
もちろん換装する予定です。

まとめ

SNS作りました。OpenAPI便利ですね。

今後の目標

  • さすがにフロントエンドを改善する
  • フロントエンドから決めていく
  • 次のリリース(笑)ではHash型を使う

次回予告

明日はいよいよアドカレ最終日、ゆかりおんさんによる締めが入ります。

最近は毎日研究の進捗を出さないといけないから普通に破滅していると思うが果たしてどうなってしまうのか!

記事は上がるのか!

落とした原稿はいつ上がるのか!

次回、ゆかりおん 死す。デュエルスタンバイ!

名古屋大学 情報学部 CS科 編入後のあれこれ

はじめの前に

こちらは鉄球連盟アドベントカレンダーの6日目の記事です。

はじめに

こんにちは。Mackyson(まっきぃ[さそ]ん)です。
僕は香川高専 電気情報工学科から2021年4月に名古屋大学 情報学部 コンピュータ科学科 知能システム系に編入学しました。

尾張に行って、終わりになりました。

僕の代の編入試は完全にバグっていて、COVID-19の影響で試験がオンライン面接だけになったり、定員2名のCS科になぜか3人合格していたりしています。
何が言いたいかというと、編入するまでの情報には何の再現性もなく、ほとんど何の価値もないということです。
3/23追記:なんか最近にわかにアクセスが伸びていますが、過去問などを期待して開いた編入志望の方々は合格後にまたいらしてください。

一方、編入してからの情報については、かなり再現性や信頼性のある話ができると思います。ごく一部の人にとって価値があるでしょうから、ここに書いておきたいと思います。逆に言うと、ほとんどの人には関係ない話ですが、アドカレ記事だし一応目を通してくれる人の存在を踏まえて読み物として面白ければいいなと願っています。

単位認定

留年したくないと思っている人は、編入してから一番気になるのが単位認定だと思います。履修計画が変わってきますので……
単位認定は"既修科目"と"その単位で読み替えてほしい名大の科目"の対応を書いて、シラバスと一緒に提出すればOKでした。他にもいろいろと困ったことがありましたが教務学生係宛にメールすれば解決します。
学科長がシラバスを精査して”認定いけるかもサイン”を出した科目だけが、教授会で審査され、認定か不認定に至ります。学科長フィルターを超えれば、かなり打率は高めだと思います。
そんなこんなで、授業計画を決めるためにメールを飛ばしまくり、 爆速で学科長面談が実現しました。そのときに
f:id:Mackyson:20211214012209p:plain こちらの中途半端に攻めたノートパソコンを教員室まで持っていきました。これしかないからね。奇跡的にも、先生の分野的にフィックスターズさんと関わりがあるらしく、話題ができました。

認定のルール

認定にはいろいろと細かいルールがありました。列挙していきます。

  • 全学科目は(少なくともCS科は)卒業分までを一括認定
  • 専門/専門基礎/関連専門科目は合計30単位までしか読み替えられない
  • 2単位の科目を1単位の科目2つに読み替えることはできない(ただし、自然言語処理1と自然言語処理2みたいな続き物の場合は可)
  • 他学部の科目に読み替えることはできない
  • 卒業研究の読み替えはできない
  • 実験はほぼ読み替え無理

秋1期のFPGA実験が認定されないのが確定したため、内心かなりつらかったです。ただ、実際やってみると、 精神が健全ならそんなに難しい実験ではなかったです。

認定結果

激闘の末、こうなりました。

f:id:Mackyson:20211214012224p:plain

結構認定されているように見えますが、先端計算機アーキテクチャ以下は全部(知シスの)必修ではないので、あんまり楽にはなれません。まぁ勉強しに来たんでいいんですが……
電気科でがんばった電磁気、電気・電子回路、実験の単位は、無事に虚空へ消えました。

電気科の君へ

電気情報工学科(略称:電気 )という学科にいたため、知能システムっぽい話は高専では全くやりませんでした。それどころか、その辺の基礎となりそうなオートマトンすらやっていません。一方、情報システムっぽい話は結構やっていて、必修が5つも認定されています。情報システム系なら、電気出身でも結構楽な生活を送れると思います。

成績表への反映

成績表にはこんな感じに載ります。

f:id:Mackyson:20211214022404p:plain

離散数学は認定されなかった科目で普通に成績が付きます。一方認定された科目は合格とだけついてGPA計算には入りません。
ここから導けることは、入学さえできたなら、編入前の成績は一切関係ないということです。高専時代の信号処理は再試すら受けずに落単しましたが、今猛烈に後悔しています。

余談:高専生は優秀?

面談に行ったとき、先生がなんども「高専生は優秀」とおっしゃっていました。じゃあもっと人数取ってくれよ。 個人的には高専生は別に優秀じゃないと思います。一般入試勢は比にならない量の勉強をしているはずだし……

でも、先生が実際にそう感じるということは、「一回やったことがある」というのがアドバンテージになっているのではないでしょうか?
すなわち、我々高専卒は卒論を書いたりとか、アホみたいに実験レポートを書かされたりとか、そういう「一回やったことがある」ことが普通の大学生より多くて、強くてニューゲームみたいなことになっているのではないかと思います。

なんでも一回やってみることで人生がお得になっていく気がします。

研究室配属バトル

これは3年生の12月に書いた記事で、執筆時点で研究室配属はまだです。ですが、研究室配属の材料はもちろん知らされています。
3/23追記:研究室配属終わりました。

CS科はCSGPAという学科独自の指標で研究室を振り分けます。
3/23追記:具体的には、研究室名とその研究室が受け入れる人数が書いてあるスプレッドシートが出されて、そこに各自が名前を書いていく感じになります。締め切り時点で人数が上限以内に収まっている研究室の志望者は全員配属確定です。収まってない研究室の志望者はCSGPAが高い人間から配属が決定し、あふれた人は残った枠を賭けて同じようにCSGPAバトルするようです。しかし、おそらくはLINEグル(的な何か)でみんな調整しているはずでしょうから、枠があまりまくるということは全然ないんじゃないかと思います。僕はLINEやってないので知りませんが……(ちなみに、今年度は僕の志望する研究室だけあふれました。僕がLINEやってないからかな?)

CSGPAは、必修の専門基礎・専門科目だけで数えたGPAです。3年次編入生が2年以下の科目をボコる(ちなみに、僕は2年必修の代数学にボコられました)不公平を防ぐため、編入生は3年の必修科目だけで計算されます。ここで大きいのが、1,2年の必修と3年の必修が被っている場合、1,2年の科目を優先することが可能(必須ではない)で、1,2年の科目を先に履修した場合は、被った3年の科目はCSGPA計算ではノーカウントになります。ただし、特に被ってないのに履修しなかった3年の科目は不合格扱いになります。編入生はCSGPAの分母が小さくてめちゃくちゃ値が動くので、常に高めをキープしないと研配属で負けます。マジで頑張ってください。
あと、某先生に人気研究室を聞いたところ、「まぁ年によって違うけど、森研究室が人気と聞く」とのことでした。それ以上の情報は得られませんでした。
3/23追記:今年は東中研、松原研、石川研が大人気で、森研、外山研、工藤グループ、榎堀グループが不人気だった気がします。

授業のレベル

代数には瀕死までボコられましたが、受けた範囲では、ほかの専門はかなり簡単です(正確に言うと、一部は簡単ではないが全部採点が甘めだとおもう)。少なくとも課題をちゃんとやれば解けるようになっています。 特に酒井先生の科目は慈悲そのものでした。
僕のTLにはよく京大の異常授業情報が流れてきますが、あの辺とはレベチです。名大ならあんまり身構えなくていいと思います。

名古屋スパ銭情報

ところで、遠くの大学に入学したとなれば、真っ先にホームスパ銭を見つけないといけませんよね。4月に3軒回ったので、レビューします。

天然温泉露天風呂 宮の湯

こちら

名大周辺からなら、ちょっと西にいって、川原通あたりから金山7番のバスに乗っていい感じのところで降りれば行けます。

サウナが横に広くていいですね。外気浴スペースはそんなに広くないですが、外気浴だけでなくスコリア溶岩寝湯でトランス状態までいく特殊コンボも入ります。

ただ、あんまり浴槽が広くなくて、好みではありませんでした。休憩スペースも、もうちょっとリッチな空間にしてくれると嬉しいです。行ったらクーポンみたいなのがもらえて、次行くときに安くなります。

大曾根温泉 湯の城

こちら

大曾根駅から徒歩五分というかなり良い立地です。

このスパ銭はパチ屋スガキヤなどが併設されており、一帯に独身成人男性のユートピアを形成しています。
ここもサウナが結構広くて、真ん中にサウナストーンがドカンと置いてあるのが特徴的です。そこそこの冷たさの水風呂のほかに、ほぼ体温みたいな感じのぬるい湯があり、いかにもって感じのサウナの雰囲気もあいまって初心者に優しいつくりだと思います。
平日金曜に霧幻狼龍(ムゲンロウリュウという、わりとキツい名前のサウナイベントを行っており、ちょっと気になっているのですが、イベント開始が20時からというのもあってなかなか体験する機会がありません。

やっぱり浴槽は若干狭いのですが、好みではない!とはならないです。でもサウナが本質的かな。

腹が立つのは漫画付きの休憩スペースに入るのに追加料金がいることです。足元見やがってよォ~~~。
あと露天風呂への扉を開放するのもやめてほしい。感染症対策なのですが……

山王温泉 喜多の湯

こちら

ここが、僕の回答です。

浴室全体のスペースでいえばそんなに広くはないのですが、風呂の密度が薄く、かなり広々としているように感じます。しかも内風呂が三種類なのに対し露天風呂は六種類あるという、この英断。讃えたい。ジェットバスが露天風呂になっているのは初めて見ました。
三軒回った中で、一番空間的な広さを感じました。気持ちいいですね。
そしてサウナですが、上記二つとは違い、上下に長いタイプでした。入れる人数に限りがあって、混んでるときはなかなか厳しいです。空いてるときを狙いましょう。水風呂は14~15度とやや低めの設定で、結構効きます。たまりませんな。
そして、無料の休憩所には柔らかなソファと漫画があります。最近の漫画も多く取り揃えていて、大変ありがたい限りです。今度、呪術廻戦を読みに行きます。

そして、このスパ銭の何より優れているところはその立地にあります。このスパ銭、山王駅から徒歩三分という好立地なのですが、この山王駅というのは金山と名駅の間にあります。 金山には、金山コムテックがあり、つまりDDRがあります。名駅周辺にはほぼ何でもありますし、市バスも夜行バスもここをハブとしています。
つまり、このスパ銭を絡めて「金山でDDRして昼飯を食べる→山王で風呂→名駅周辺で晩飯→名古屋大学行きのバスで帰る」という神の一手が考えられます。 実際にこれを全部繋げたことはないですが、「金山DDR→喜多の湯」や、「喜多の湯→名駅で晩飯→夜行バスで帰省」という黄金のコンボを何度かやりました。 特に帰省する前には必ず寄っているので、毎度、帰省が楽しみで仕方ないです。

以上! まだ行ってはいないのですが、名古屋にはサウナラボやウェルビーといった、有名なサウナがありますし、在学中に他にもいろいろ行ってみたいと思います!

次回予告

明日は鉄球連盟が誇る文豪、カプチーノPによる、地獄のR18リレー小説のPart2が上映されます。お楽しみに。