kun432's blog

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

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

Alexa(Voiceflow)でKubernetesのPodを増やしたり減らしたりする

f:id:kun432:20201226140822p:plain

スマートスピーカー Advent Calendar 2020 に空きがあったので、クリスマスもう終わってますが後から追加です。4日目の記事です。

KubernetesやってるとPodの増減とかをAlexaでやってみたくなりますよねー。ということで、もう何番煎じかわかりませんが、やってみました。

目次

動画

Kubernetesクラスタ構築

Vagrantを使ってローカルにKubernetesクラスタを構築します。以下のレポジトリをcloneしてください。

このリポジトリのVagrantfileでは

  • master x 1台、worker x 2台、おまけ x 1台
  • プロビジョニングでKubernetesのクラスタ構築を自動で実施

を行っていますので、vagrant upすれば、すぐにクラスタにpodをデプロイできる状態になっています。

ただちょっとリソース的には必要かもなので、マシンパワーが心許ない場合はmasterworker-1だけupすればいいかなと思います。

$ git clone https://github.com/kun432/k8s-book-vagrant
$ cd k8s-book-vagrant

# リソース余裕な方
$ vagrant up

# リソース辛い・・・な方
$ vagrant up master
$ vagrant up worker-1

vagrant upで全部上げるとこんな感じです。

$ vagrant status
Current machine states:

master                    running (virtualbox)
worker-1                  running (virtualbox)
worker-2                  running (virtualbox)
test                      running (virtualbox)

(snip)

masterのvagrantユーザでkubectlが叩けるようにしてありますので、masterにsshします。

$ vagrant ssh master

nodeを見てみましょう。

$ kubectl get node
NAME       STATUS   ROLES    AGE     VERSION
master     Ready    master   14m     v1.18.0
worker-1   Ready    <none>   2m16s   v1.18.0
worker-2   Ready    <none>   2m16s   v1.18.0

適当なdeploymentをでっちあげます。Kubernetesの公式にあるnginxのdeploymentを今回はそのまま使いましょう。

中身はこんな感じですね。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

namespace: defaultにdeployment名: nginx-deploymentで、nginxのpodが3台立ち上がるようになっています。適用してみましょう。

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/master/content/ja/examples/controllers/nginx-deployment.yaml
deployment.apps/nginx-deployment created

deploymentを見てみましょう

$ kubectl get deploy
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           30s

はい、3台のnginxのpodが立ち上がっています。

これで、クラスタとpodの準備は完了です。

kubectl proxyでapiserverにかんたんにアクセスする

Kubernetesのクラスタに対する操作は、masterのkube-apiserverに対して行います。kubectlも裏側ではkube-apiserverに対してリクエストを送っています。つまりcurlでkube-apiserverのREST APIにアクセスすればいいということになります。kubectlの設定を見てみます。

$ kubectl config view  | grep server
    server: https://10.240.0.21:6443

kube-apiserverのURLがわかりました。ではcurlで叩いてみましょう。

$ curl -k https://10.240.0.21:6443/api/
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {

  },
  "status": "Failure",
  "message": "forbidden: User \"system:anonymous\" cannot get path \"/api/\"",
  "reason": "Forbidden",
  "details": {

  },
  "code": 403

anonymousなアクセスは許可されていないのでダメだと言われます。kubectlでkube-apiserverにアクセスする場合は.kube/configの設定が利用されるのですが、ここにapiserverにアクセスするためのクライアント証明書が含まれていて、これで認証を行っています。つまり認証をパスしないとAPIは叩けないということです。

認証方式には複数の種類があり本来はきちんと認証を設定すべきですが、今回はデモなのでちょっと楽をします。kubectl proxy &を実行してください。

$ kubectl proxy &

[1] 8200
Starting to serve on 127.0.0.1:8001

これによりkube-apiserverへのリバースプロキシが用意され、http://127.0.0.1:8001でアクセスできるようになります。やってみましょう。

$ curl  http://127.0.0.1:8001/api/
{
  "kind": "APIVersions",
  "versions": [
    "v1"
  ],
  "serverAddressByClientCIDRs": [
    {
      "clientCIDR": "0.0.0.0/0",
      "serverAddress": "10.240.0.21:6443"
    }
  ]
}

はい、HTTPSではなくHTTPの8001番ポート、かつ認証不要でアクセスできるというわけですね。非常に開放的な状態ですがw、あくまでもデモだということでこのまま進めます。

podを増やす

ではAPI経由でpodを操作してみましょう。

まず、現在のdeploymentのpod数(replicas)を取得してみます。/apis/apps/v1/namespaces/namespace名/deployments/deployment名/scaleをGETします。

$ curl  http://127.0.0.1:8001/apis/apps/v1/namespaces/default/deployments/nginx-deployment/scale
{
  "kind": "Scale",
  "apiVersion": "autoscaling/v1",
  "metadata": {
    "name": "nginx-deployment",
    "namespace": "default",
    "selfLink": "/apis/apps/v1/namespaces/default/deployments/nginx-deployment/scale",
    "uid": "dafaae12-48ae-4697-a642-b81bf819a764",
    "resourceVersion": "1352",
    "creationTimestamp": "2020-12-25T16:24:12Z"
  },
  "spec": {
    "replicas": 3
  },
  "status": {
    "replicas": 3,
    "selector": "app=nginx"
  }
}

spec.replicasが設定されている(立ち上がっているべき)pod数、status.replicasが実際に立ち上がっているpod数です。3個のpodが上がるように定義されていることがわかります。

では、podを増やしてみましょう。同じAPIエンドポイントにPATCHリクエストを送ります。PATCHの場合はContent-Type: application/strategic-merge-patch+jsonヘッダを指定して、-dで更新する内容を記載します。

$ curl -X PATCH \
-H 'Content-Type: application/strategic-merge-patch+json' \
http://127.0.0.1:8001/apis/apps/v1/namespaces/default/deployments/nginx-deployment/scale \
-d '{"spec":{"replicas":5}}'

こんな感じでstatus.replicasが更新されていればOKです。

(snip)
  "spec": {
    "replicas": 5
  },
  "status": {
    "replicas": 3,
    "selector": "app=nginx"
  }
}

実際に増えているか見てみましょう。

$kubectl get deploy
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   5/5     5            5           47m

増えていますね。curlでも見てみます。

$ curl  http://127.0.0.1:8001/apis/apps/v1/namespaces/default/deployments/nginx-deployment/scale
(snip)
  "spec": {
    "replicas": 5
  },
  "status": {
    "replicas": 5,
    "selector": "app=nginx"
  }
}

実行直後はstatus.replicasが3だったのが5になっていますね。あるべきpod数(spec.replicas)と実際のpod数(status.replicas)の違いはこういうことです。

詳細はKubernetesのAPIリファレンスを見てください。

kube-apiserverへのアクセスを用意する

あとはAlexa、というか、LambdaからこのAPIをよしなに叩けばいいわけですが、今回はローカル環境にKubernetesクラスタを立ち上げているので、インターネット側からの通信が通るようにしてあげる必要があります。

そこでngrokを使って、ローカルからトンネルをはってインターネット上のURLからアクセスできるようにします。

上記にアクセスしてログインもしくはサインアップします。

f:id:kun432:20201226023322p:plain

セットアップのページが開きます。ここのLinux版のダウンロードリンクをコピーします。

f:id:kun432:20201226023749p:plain

master上でコピーしたURLに対してwgetします。URLが固定なのかわからないので、x のところは読み替えてください。

$ wget https://bin.equinox.io/x/xxxxxxxx/ngrok-stable-linux-amd64.zip

unzipします。

$ sudo apt-get install unzip
$ unzip ngrok-stable-linux-amd64.zip
$ ls -l
-rwxr-xr-x 1 vagrant vagrant 26683198 Oct  9  2019 ngrok
(snip)

セットアップのページの「2. Connect your account」にあるコマンドをmasterで実行します。これにより、ngrokを使うためのトークンが設定されます。

f:id:kun432:20201226024328p:plain

$ ./ngrok authtoken XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Authtoken saved to configuration file: /home/vagrant/.ngrok2/ngrok.yml

ではngrokを立ち上げます。セットアップのページに記載されているものからは少し変えています。

$ ./ngrok http -host-header=localhost -auth="ユーザ名:パスワード" 8001

※ユーザ名とパスワードは自由に設定してください。

まず。kubectl proxyで立ち上げたエンドポイントはhostヘッダがlocalhost以外の場合、リクエストを拒否します。-host-headerオプションをつけることでngrokからアクセスする際にhostヘッダを書き換えてくれます。

あと、現在の状態だとapiserverに認証なしでアクセスできて「何でも」できてしまいます。そこで-authでBASIC認証をかけています。ただし、ないよりはマシ程度の気休めにしかならないと思うので、くれぐれもご注意ください。

ngrokを立ち上げるとこんな感じで表示されます。真ん中にあるのがngrokが発行したインターネットからアクセス可能なURLになります。

f:id:kun432:20201226123112p:plain

別のターミナルを開いてmasterにsshして、実際にcurlでアクセスできるか確認してみましょう。BASIC認証をかけているので以下のようなAuthorizationヘッダを付与する必要があります。

Authorization: Basic XXXXXXXXXXXXXX

BASICのあとに指定する文字列は、"ユーザ名:パスワード"をbase64エンコーディングした文字列になりますので、以下で取得できます。

$ echo -n "ユーザ名:パスワード" | base64
XXXXXXXXXXXXXX

こんな感じで返ってくればOKです。これでインターネットからkube-apiserverにアクセスできるようになりました。

$ curl -H 'Authorization: Basic XXXXXXXXXXXXXX' https://xxxxxxxx.ngrok.io/apis/apps/v1/namespaces/default/deployments/nginx-deployment/scale
(snip)
  "spec": {
    "replicas": 5
  },
  "status": {
    "replicas": 5,
    "selector": "app=nginx"
  }
}

ngrok側でもアクセスが来たことがわかりますね。

f:id:kun432:20201226124153p:plain

Alexaからkube-apiserverにアクセスする

あとはAlexaスキルのバックエンドのAWS LambdaからngrokのURLにアクセスすればOKです。今回はLambdaを使わずにVoiceflowでやってみました。Podの数を参照するところと、変更するところでAPI Blockを使ってngrokのURLにアクセスしてます。

f:id:kun432:20201226125423p:plain

参照するところはこんな感じ。ngrokのURLとBASIC認証のbase64文字列はSet Blockで最初に変数にしてあります。リクエストの結果はresponse.spec.replicasで取得できますので、変数pod_numにいれて喋らせてます。

f:id:kun432:20201226130629p:plain

ユーザからの発話をうけとるところです。変更したいpod数をスロット"{number}"で受け取ります。

f:id:kun432:20201226130908p:plain

f:id:kun432:20201226131024p:plain

ユーザから指定されたpod数で今度は変更を行います。変更の場合はPATCHリクエストになり、Content-typeヘッダを追加します。

f:id:kun432:20201226131559p:plain

実際の変更内容はBodyタブで指定します。スロット"{number}"を含めた変更内容をここで指定します。

f:id:kun432:20201226131734p:plain

これで終わりです。あとはkubectl get deploy -wしながら、alexaスキルを起動してpodの増減が行なわれることを確認してください。

vagrant@master:~$ kubectl get deploy -w
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   5/5     5            5           11h
nginx-deployment   5/4     5            5           12h
nginx-deployment   5/4     5            5           12h
nginx-deployment   4/4     4            4           12h
nginx-deployment   4/2     4            4           12h
nginx-deployment   4/2     4            4           12h
nginx-deployment   2/2     2            2           12h
nginx-deployment   2/1     2            2           12h
nginx-deployment   2/1     2            2           12h
nginx-deployment   1/1     1            1           12h

終わったらctrl+cでngrokは止めておくのをお忘れなく。Vagrant環境の削除は以下でOKです。

$ vagrant destroy -f

補足

  • kubectl proxyを使わずにAPIサーバに直接接続させることも可能です。ただし、
    • 認証を行う必要があります。やり方は色々ありますが、tokenを使って"Authorization: Bearer"で指定するのが一番簡単だと思います。defaultのservice accountでは権限が足りないので、clusterRoleBindingを付与したservice accountを作成して、そのsecretを取得すれば良いと思います。
    • ngrokでは、tlsやtcpでリバースプロキシさせることも可能なんですが・・・
      • tlsの場合は有償プランが必要です・・・
      • tcpの場合はkube-apiserverでSSLを終端させることになりますが、kube-apiserverの証明書はオレオレ証明書なので、Voiceflowだとつなげませんでした・・・
      • Lambdaでnode.jsとかであればできると思います。例えばaxiosを使う場合だとhttpsAgentrejectUnauthorized: trueを指定すればいけるみたいです。
    • ということで、今回はkubectl proxyを使っています。
  • BASIC認証だけなのでくれぐれもご注意ください。

まとめ

スマートスピーカーよりも Kubernetes の話のほうが多かった気がしますが、気にしない!

AlexaでこういうのがコントロールできるとChatOps的で楽しいですね。