かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Azure Kubernetes Service のハンズオンしてきた!その復習

お盆で帰省してたタイミングで丁度下のイベントが行われていたので参加者として参加してきました!

hiroshima-jug.connpass.com

今まで Azure だと Web App とかで割となんとかなっていたので使うことはなかったのですが興味はあったので丁度いいと思ったのがきっかけ。

復習もかねて実際に使った以下のリポジトリーの内容を見ながら自分でもやってメモっておこうと思います。

github.com

Azure Kubernetes Service (AKS)

名前のとおり Azure の Kubernetes のサービス。 ノード数とノードのスペックくらいを設定しておけば、あとは割とよしなにやってくれるみたい。急激にスパイクしたときは Azure Container Instance のほうに展開するとかいうこともできるみたい。ノード立ち上げたりすると時間がかかるしね。

作ってみよう

さくっと Kubernetes Service を作ってみた

f:id:okazuki:20190812090648p:plain

f:id:okazuki:20190812090710p:plain

モブプログラミング形式でみんなでやったハンズオンでは、確か下の HTTP の機能を有効にしたけど…

f:id:okazuki:20190812091048p:plain

本番では、HTTPS の構成をしたほうがよさそうということがドキュメントに書いてあった。

こんな感じで。

docs.microsoft.com

とりあえず ON にして始めてみよう。

Azure Container Registry の作成

プライベートな DockerHub みたいな ACR も作っておきます。 これは特筆事項もとくにないくらい作るだけです。

作業用マシンを作ろう

私は Azure 上に Linux VM を一台立ててるのでそれを使います。 az コマンドのインストールと

docs.microsoft.com

docker を入れておきます。

docs.docker.com

ACR へのログインを docker コマンドでしておきます。ACR のアクセスキーのところで見れます。管理者ユーザーっていうのを有効にするみたい。

docker login -u ユーザー名 -p "パスワード" ACRの名前.azurecr.io

実際に値を当てはめるとこんなかんじ。

sudo docker login -u kazukicr -p "sugo-kunagai-portal-kara-toreru-pasuwa-do-dayo!" kazukicr.azurecr.io

適当に ACR に push

ということでついに docker push の機運が高まってきました。Azure Functions でやってみます。

以下のように --docker オプションをつけておくと docker ファイルもできます。便利。

func init --docker

そして、何もないと寂しいので func new で HttpTrigger の関数でも追加しておきます。そして以下のコマンドをたたいてビルド

sudo docker build .

ビルドが通ったので、とりあえずタグ付けとか docker push するスクリプトも書いておきます。これも寺田さん作のもの

#!/bin/bash
set -e

if [ "$1" = "" ]
then
    echo "./build-create.sh [version-number]"
    exit 1
fi
export VERSION=$1

DOCKER_IMAGE=okazukirc/dockerfunc
DOCKER_REPOSITORY=kazukicr.azurecr.io

docker build -t $DOCKER_IMAGE:$VERSION . -f ./Dockerfile
docker tag $DOCKER_IMAGE:$VERSION $DOCKER_REPOSITORY/$DOCKER_IMAGE:$VERSION
docker push $DOCKER_REPOSITORY/$DOCKER_IMAGE:$VERSION

docker rm $(docker ps -aq)
docker images | awk '/<none/{print $3}' | xargs docker rmi

bash のスクリプト慣れないなぁ。chmod して sudo ./docker-build.sh 1.0 してビルド成功!!

ACR の画面を見るとちゃんと push されてました。めでたい。

f:id:okazuki:20190815131906p:plain

ちょっとローカルで動かしてみましょう。以下のコマンドをたたいて…

$ sudo docker run -p 8080:80 kazukicr.azurecr.io/okazukirc/dockerfunc:1.0

curl でたたくと…

$ curl http://local/Hello?name=aaaaaa
Hello, aaaaaa

動きましたね。

ついに Kubernetes

kubectl を入れます。コマンドでさくっと

$ az aks install-cli

コマンドが入ったので AKS への認証情報をゲットしましょう。

$ az aks get-credentials --resource-group AKS-rg --name kazukiaks
Merged "kazukiaks" as current context in /home/okazuki/.kube/config

ノードも取れました。

$ kubectl get node
NAME                       STATUS   ROLES   AGE    VERSION
aks-agentpool-51334753-0   Ready    agent   3d4h   v1.12.8
aks-agentpool-51334753-1   Ready    agent   3d4h   v1.12.8
aks-agentpool-51334753-2   Ready    agent   3d4h   v1.12.8
virtual-node-aci-linux     Ready    agent   3d4h   v1.13.1-vk-v0.9.0-1-g7b92d1ee-dev

AKSからACRへのアクセスの認証情報を追加します。

$ kubectl create secret docker-registry \
>   docker-reg-credential \
>   --docker-server=kazukicr.azurecr.io \
>   --docker-username=kazukicr \
>   --docker-password=your-password \
>   --docker-email=k_ota28@hotmail.com

ここら辺から、デプロイメントやサービスなどの定義の YAML を適用するのですが、ハンズオン時には出来上がった YAML の一部を編集して使ってたのでよくわからんって感じになりますね。

まず、Deployment については下を見ました。

kubernetes.io

今回の場合はこんな感じだろうか??

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dockerfunc-deployment
  labels:
    app: dockerfunc
spec:
  replicas: 3
  selector:
    matchLabels:
      app: dockerfunc
  template:
    metadata:
      labels:
        app: dockerfunc
    spec:
      securityContext:
        runAsUser: 1000
      imagePullSecrets:
        - name: docker-reg-credential
      containers:
      - name: dockerfunc
        image: kazukicr.azurecr.io/okazukirc/dockerfunc:1.0
        ports:
        - containerPort: 80

imagePullSecrets が先ほど kubectl で作った secret の名前です。runAsUser は、ルートで動かさないという感じ。1000 にしてるってことは最初に作られたユーザーアカウントってことなんだろう。

kubectl apply -f ファイル名 で適用できるので、上記ファイルを deploy.yaml という名前で保存している場合は以下のようなコマンドで適用できます。

$ kubectl apply -f deploy.yaml 
deployment.apps/dockerfunc-deployment created

deployment がちゃんとできてるかコマンドを打って確認します。

$ kubectl get deploy
NAME                    DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
dockerfunc-deployment   3         3         3            0           74s

出来てるっぽい。Pod は…?

$ kubectl get po
NAME                                    READY   STATUS             RESTARTS   AGE
dockerfunc-deployment-76bb667bd-6cl68   0/1     CrashLoopBackOff   4          2m43s
dockerfunc-deployment-76bb667bd-9jnsh   0/1     CrashLoopBackOff   4          2m43s
dockerfunc-deployment-76bb667bd-vtcg8   0/1     CrashLoopBackOff   4          2m43s

CrashLoopBackOff なので激しく失敗してますね…。エラーのときは kubectl description コマンドでログ見てみるといいといわれたので見てみます。

$ kubectl describe pods dockerfunc-deployment-76bb667bd-6cl68

結果はこれ…よくわからない...

Name:           dockerfunc-deployment-68984c94bd-4vbr2
Namespace:      default
Priority:       0
Node:           aks-agentpool-51334753-1/10.240.0.66
Start Time:     Thu, 15 Aug 2019 05:18:22 +0000
Labels:         app=dockerfunc
                pod-template-hash=68984c94bd
Annotations:    <none>
Status:         Running
IP:             10.240.0.68
Controlled By:  ReplicaSet/dockerfunc-deployment-68984c94bd
Containers:
  dockerfunc:
    Container ID:   docker://c62fc1f1d2a3818651d0b703140b3119ad3755ffb8cb99efc49ea563c15c2ddc
    Image:          kazukicr.azurecr.io/okazukirc/dockerfunc:1.0
    Image ID:       docker-pullable://kazukicr.azurecr.io/okazukirc/dockerfunc@sha256:2ef5515278a6f17053dfc14289fdf5f77ea7d127819d75f6951b1beac00a212e
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Terminated
      Reason:       Error
      Exit Code:    1
      Started:      Thu, 15 Aug 2019 05:19:05 +0000
      Finished:     Thu, 15 Aug 2019 05:19:05 +0000
    Last State:     Terminated
      Reason:       Error
      Exit Code:    1
      Started:      Thu, 15 Aug 2019 05:18:39 +0000
      Finished:     Thu, 15 Aug 2019 05:18:39 +0000
    Ready:          False
    Restart Count:  3
    Limits:
      cpu:  100m
    Requests:
      cpu:        100m
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-psnwx (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             False 
  ContainersReady   False 
  PodScheduled      True 
Volumes:
  default-token-psnwx:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-psnwx
    Optional:    false
QoS Class:       Burstable
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type     Reason     Age               From                               Message
  ----     ------     ----              ----                               -------
  Normal   Scheduled  50s               default-scheduler                  Successfully assigned default/dockerfunc-deployment-68984c94bd-4vbr2 to aks-agentpool-51334753-1
  Normal   Pulled     8s (x4 over 49s)  kubelet, aks-agentpool-51334753-1  Container image "kazukicr.azurecr.io/okazukirc/dockerfunc:1.0" already present on machine
  Normal   Created    8s (x4 over 49s)  kubelet, aks-agentpool-51334753-1  Created container
  Normal   Started    7s (x4 over 48s)  kubelet, aks-agentpool-51334753-1  Started container
  Warning  BackOff    7s (x5 over 47s)  kubelet, aks-agentpool-51334753-1  Back-off restarting failed container

とりあえず RunAsUser を外してみたら動いた…う~ん。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dockerfunc-deployment
  labels:
    app: dockerfunc
    version: v1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: dockerfunc
  template:
    metadata:
      labels:
        app: dockerfunc
    spec:
#      securityContext:
#        runAsUser: 1000
      imagePullSecrets:
        - name: docker-reg-credential
      containers:
      - name: dockerfunc
        image: kazukicr.azurecr.io/okazukirc/dockerfunc:1.0
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: 100m
          limits:
            cpu: 100m

apply したら動いた

$ kubectl get po
NAME                                     READY   STATUS    RESTARTS   AGE
dockerfunc-deployment-674d45656d-jgb9b   1/1     Running   0          119s
dockerfunc-deployment-674d45656d-lsbf4   1/1     Running   0          115s
dockerfunc-deployment-674d45656d-mnb8v   1/1     Running   0          2m1s

ポートフォワーディングして動いてるか確認しましょう。

$ kubectl port-forward dockerfunc-deployment-674d45656d-jgb9b 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

curl でたたいてみると…

$ curl http://localhost:8080/api/Hello?name=okazuki
Hello, okazuki

動いた!!

サービス

pod をまとめるサービスかな。それの定義を作りましょう。

apiVersion: v1
kind: Service
metadata:
  labels:
    app: dockerfunc-service
  name: dockerfunc-service
spec:
  ports:
  - port: 80
    name: http
    targetPort: 80
  selector:
    app: dockerfunc
  sessionAffinity: None
  type: LoadBalancer

dockerfunc の v1 で選択したやつをさくっとまとめておきます。最後の type: LoadBalancer は本番では使わないで!って言われたけど、とりあえず動きを確認するために設定。これをしてると簡単に外から叩ける。

適用します。

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

しばらく待つと IP が割り当てられます。(今回は 13.71.149.89)

$ kubectl get service
NAME                  TYPE           CLUSTER-IP   EXTERNAL-IP    PORT(S)        AGE
dockerfunc-service   LoadBalancer   10.0.52.83   13.71.149.86   80:31505/TCP   7m4s
kubernetes            ClusterIP      10.0.0.1     <none>         443/TCP        3d5h

curl でたたくと…

$ curl http://13.71.149.86/api/Hello?name=kubernates
Hello, kubernates

動いた!!

では、サービスを LoadBalancer で公開するのをやめます。ClusterIP にしておくのがよさそう。ClusterIP にしたら一度サービス削除して再度アプライしました。

$ kubectl delete service dockerfunc-service
$ kubectl apply -f service.yaml

Ingress を作成

Ingress がサービスに処理を割り振ってくれるような動きをしてくれるものみたいです。これを作って、ここにサービスの割り振りを設定することでちゃんと外からアクセスできるようになります。

Ingress を作りにあたってドメインをゲットしましょう。ポータルの AKS を開くと HTTP アプリケーションのルーティング ドメイン という項目があります。これを控えておきます。

f:id:okazuki:20190815150534p:plain

そして、dockerfunc-service に対してアクセスできるようにしましょう。Ingress を定義する YAML を定義して…

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: dockerfunc
  annotations:
    kubernetes.io/ingress.class: addon-http-application-routing
spec:
  rules:
  - host: dockerfunc.ada93e44ae4b4d9ea00a.japaneast.aksapp.io
    http:
      paths:
      - backend:
          serviceName: dockerfunc-service 
          servicePort: 80
        path: /

apply しましょう。

$ kubectl apply -f ingress.yaml
ingress.extensions/dockerfunc created

kubectl get ing で Ingress の状態が見れます。

$ kubectl get ing
NAME         HOSTS                                                 ADDRESS          PORTS   AGE
dockerfunc   dockerfunc.ada93e44ae4b4d9ea00a.japaneast.aksapp.io   104.41.172.168   80      52s

では、curl でアクセスしてみましょう。

$ curl http://dockerfunc.ada93e44ae4b4d9ea00a.japaneast.aksapp.io/api/Hello?name=Ingress
Hello, Ingress

動いた!!

バージョンアップしてみよう

ちょっと Azure Functions のコードを変えていきます。

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace DockerFunc
{
    public static class Hello
    {
        [FunctionName("Hello")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;

            return name != null
                ? (ActionResult)new OkObjectResult($"こんにちは, {name}")
                : new BadRequestObjectResult("Please pass a name on the query string or in the request body");
        }
    }
}

ビルドスクリプトを実行して version 2.0 を作ります。

$ sudo ./docker-build.sh 2.0

できました。

f:id:okazuki:20190815151254p:plain

deployment を新しくして…

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dockerfunc-deployment
  labels:
    app: dockerfunc
    version: v1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: dockerfunc
  template:
    metadata:
      labels:
        app: dockerfunc
        version: v2
    spec:
#      securityContext:
#        runAsUser: 1000
      imagePullSecrets:
        - name: docker-reg-credential
      containers:
      - name: dockerfunc
        image: kazukicr.azurecr.io/okazukirc/dockerfunc:2.0
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: 100m
          limits:
            cpu: 100m

apply してみましょう。

$ kubectl apply -f deploy.yaml 
deployment.apps/dockerfunc-deployment configured

curl でたたいてみると…

$ curl http://dockerfunc.ada93e44ae4b4d9ea00a.japaneast.aksapp.io/api/Hello?name=Ingress
こんにちは, Ingress

ふむ…

deployment のラベルを完全に別名にして、サービスを新たに作って Ingress で新しいサービスを指し示すようにしたりすると古いサービスに戻すとかもできそう。

まとめ

まだまだ、いろいろな機能があるんだろうね…。 でも、とっかかりにとてもいいイベントでした!!

ちなみに実際には、このほかにも Azure DevOps を使って AKS に自動ビルドして自動デプロイするようなものもやりました。 一日なのに盛沢山。