読者です 読者をやめる 読者になる 読者になる

めもめも

このブログに記載の内容は個人の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

Google Container Engineで五目並べアプリのAPIサーバーを作るデモ

何の話かというと

Dockerコンテナでアプリを作ると便利ですが、何でもかんでもコンテナに突っ込むと(たとえば、RDBとか)面倒な事も多くなります。

・スケーラビリティが必要
・機能単位のリファクタリング/アップデート(マイクロサービス化)が必要

という部分にフォーカスしてコンテナ化して、その他のパーツは、コンテナ以外の環境と組み合わせた方が幸せになれるかも知れません。

というわけで、GCPを利用して、こんな感じのアプリケーション環境を構築するデモの手順を紹介します。

・五目並べゲームのAPIサーバーを作ります。
・ゲームの進行を管理するフロントエンドと、コンピュータープレイヤーの思考ルーチン(AI)を提供するバックエンドをGoogle Container Engine (GKE) 上のコンテナでデプロイします。
・ゲームのステータスは、Cloud Datastore(NoSQL)に保存します。

デモのシナリオはこんな感じ。

・はじめは(ゲームのリリーススケジュールにAIの開発が間に合わなかったため!)ランダムな手を打つダミーのAIでゲームをリリースします。
・その後、ちゃんとしたAIの開発が終わって、バックエンドのAIコンテナをこっそりアップデートします。
・KubernetesのRolling Update機能を使うので、ゲームのプレイヤーは、ゲームの途中で、突然、コンピューターの打つ手が良くなることに気づきます。

ちなみに、バックエンドのAIのサンプルコードは「なんちゃって」です。TensorFlowが動いているわけではありません。また、以下の作業の前提として、GCPのプロジェクト作成、課金設定、Container Engine APIの有効化が必要です。

コンテナイメージの作成と動作確認

GCPでは、Cloud Shellと呼ばれる作業用インスタンスが無料で利用できます。Cloud Shell上では、Dockerが動いているので、Dockerfileからイメージを作成することもできます。

参考:Google Cloud Platform Japan Blog - Cloud Shell が GA リリース、料金は無料に

Cloud Consoleで下記のボタンをポチッとすると、Cloud Shellが起動します。Cloud Shellのインスタンスは、数時間アクセスしていないと自動的に削除されますが、ホームディレクリーの内容は保存されるようになっています。

次の手順でソースをダウンロードして、コンテナイメージを3つ作成します。

$ git clone https://github.com/enakai00/k8s-gobang-app-example.git
$ cd k8s-gobang-app-example/
$ docker build -t frontend:v1.0 frontend/
$ docker build -t backend:v1.0 backend-dummy/
$ docker build -t backend:v1.1 backend-smart/

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
backend             v1.1                c1c5f9555252        3 seconds ago       286.2 MB
backend             v1.0                6c30c63cd6dc        34 seconds ago      286.1 MB
frontend            v1.0                0982f697e8dc        38 seconds ago      286.2 MB
debian              8.4                 7a4c9a4d5e7a        11 weeks ago        125.1 MB

・frontend:v1.0 ⇒ フロントエンド
・backend:v1.0 ⇒ ダミーAIのバックエンド
・backend:v1.1 ⇒ ちゃんとしたAIのバックエンド

作成したイメージをまずは、Cloud Shellのローカルインスタンス上のDockerで起動して、動作確認を行います。最初の "hogehoge" には、GCPのプロジェクトIDを指定してください。

$ export PROJECT_ID="hogehoge"
$ docker run -d --name backend -e PROJECT_ID=$PROJECT_ID backend:v1.0
$ docker run -d --name frontend -p 8080:8080 -e PROJECT_ID=$PROJECT_ID --link backend:backend frontend:v1.0
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                
    NAMES
9f5b27a40326        frontend:v1.0       "/opt/gobang/bin/fron"   4 seconds ago       Up 4 seconds        0.0.0.0:8080->8080/tcp   frontend
690787b5415b        backend:v1.0        "/opt/gobang/bin/back"   11 seconds ago      Up 10 seconds       8081/tcp                 backend

かっちょいいUIのクライアントAPPはまだないので、コンソール上で動作するCUIのアプリでゲームを遊んでみます。環境変数 API_URL でフロントエンドのAPIを指定して実行します。

$ API_URL=http://localhost:8080/api/v1 client/client.py
Welcome to the five-stone game.
Game ID (0:new game)? 0
Your game ID is 5649391675244544

  0 1 2 3 4 5 6 7 8 9
0 - - - - - - - - - - 
1 - - - - - - - - - - 
2 - - - - - - - - - - 
3 - - - - - - - - - - 
4 - - - - - - - - - - 
5 - - - - - - - - - - 
6 - - - - - - - - - - 
7 - - - - - - - - - - 
0 - - - - - - - - - - 
8 - - - - - - - - - - 
9 - - - - - - - - - - 
(q:quit) x(0-9), y(0-9)? 4,5

  0 1 2 3 4 5 6 7 8 9
0 - - - - - - - - - - 
1 - - - - - - - - x - 
2 - - - - - - - - - - 
3 - - - - - - - - - - 
4 - - - - - - - - - - 
5 - - - - o - - - - - 
6 - - - - - - - - - - 
7 - - - - - - - - - - 
8 - - - - - - - - - - 
9 - - - - - - - - - - 
(q:quit) x(0-9), y(0-9)? 5,5

  0 1 2 3 4 5 6 7 8 9
0 - - - - - - - - - - 
1 - - - - - - - - x - 
2 - - - - - - - - - - 
3 - - - - - - - - - - 
4 - - - - - - - - - - 
5 - - - - o o - - - - 
6 - - - - - - - - - - 
7 - - - - - - - - - - 
8 - - - - - - - - x - 
9 - - - - - - - - - - 
(q:quit) x(0-9), y(0-9)? q
Your game ID is 5649391675244544
See you again.

遊び方は画面の様子から察してください。。。。ゲームの開始時にゲームIDが割り当てられるので、途中でゲームを中断した場合でも、ゲームIDを指定してゲームを再開することができます。ここでは、ダミーAIのバックエンドを使っているので、コンピューターの手はランダムです。ローカルのDocker環境を使用する場合、新しいバックエンドに入れ替える際は、一度、コンテナを停止する必要があります。サービスを提供したままこっそりアップデートするということはできません。

動作確認ができたら、ローカルのコンテナは停止・破棄しておきます。

$ docker stop frontend backend
$ docker rm frontend backend

コンテナイメージのアップロード

GKEのクラスターからコンテナイメージを利用できるように、GCP上のプライベートレジストリーにイメージをアップロードしておきます。次のように、「gcr.io/<PROJECT ID>/名前:タグ」というイメージ名を付けて、gcloudコマンドからpushします。(gcloudコマンドを使用することにより、Cloud Shellを起動したアカウントの権限でプライベートレジストリーへのアクセスが行われます。)

$ docker tag frontend:v1.0 gcr.io/$PROJECT_ID/frontend:v1.0
$ docker tag backend:v1.0 gcr.io/$PROJECT_ID/backend:v1.0
$ docker tag backend:v1.1 gcr.io/$PROJECT_ID/backend:v1.1

$ gcloud docker push gcr.io/$PROJECT_ID/frontend:v1.0
$ gcloud docker push gcr.io/$PROJECT_ID/backend:v1.0
$ gcloud docker push gcr.io/$PROJECT_ID/backend:v1.1

$ gcloud docker search gcr.io/$PROJECT_ID
NAME                     DESCRIPTION   STARS     OFFICIAL   AUTOMATED              
<PROJECT ID>/backend                   0                    
<PROJECT ID>/frontend                  0       

アップロードしたイメージの実体は、自分のプロジェクトのCloud Storage(バケット名「artifacts.<PROJECT ID>.appspot.com」)に保存されています。

$ gsutil ls | grep artifacts
gs://artifacts.<PROJECT ID>.appspot.com/

プライベートレジストリーの内容は、Cloud Consoleからも確認できます。


コンテナクラスターの作成

Cloud Consoleからコンテナクラスターを作成します。

ゾーンは、プロジェクト作成時に指定したApp Engineのリージョンと同じ物を指定します。Cloud Datastoreは、App Engineと同じリージョンにデータを保存するので、Cloud Datastoreへのアクセスが早くなります。また、「プロジェクトへのアクセス」で「Cloud Datastore」を「有効」にしてください。これを忘れるとコンテナからCloud Datastoreへのアクセスができません。


クラスターが作成できたら、Cloud Shellから次のコマンドを実行して、クラスターを操作するための環境設定を行います。「gobang-cluster」と「us-central1-a」は、クラスター名とクラスターを作成したゾーンを指定します。

$ gcloud container clusters get-credentials gobang-cluster --zone=us-central1-a

これを実行すると設定ファイル「~/.kube/config」が用意されて、Cloud Shellからkubectlコマンドで操作できるようになります。次は、クラスター内のノードを確認する例です。

$ kubectl get nodes
NAME                                            STATUS    AGE
gke-gobang-cluster-default-pool-d43cc941-fkwy   Ready     6m
gke-gobang-cluster-default-pool-d43cc941-kbnf   Ready     6m
gke-gobang-cluster-default-pool-d43cc941-w53o   Ready     6m

バックエンドのデプロイ

デプロイ設定「config/backend-deployment.yaml」を開いて、コンテナイメージ名に含まれる<PROJECT ID>の部分を実際に使用するプロジェクトIDを修正します。

    spec:
      containers:
      - image: gcr.io/<PROJECT ID>;/backend:v1.0 <-- ココ
        name: backend-node
        ports:
        - containerPort: 8081

デプロイ設定を指定して、コンテナをデプロイします。下記のように、3個のPodがRunningになれば成功です。

$ kubectl create -f config/backend-deployment.yaml 
deployment "backend-node" created

$ kubectl get pods
NAME                            READY     STATUS    RESTARTS   AGE
backend-node-3459171109-09yzl   1/1       Running   0          5s
backend-node-3459171109-2gs4u   1/1       Running   0          5s
backend-node-3459171109-tmkjl   1/1       Running   0          5s

フロントエンドのコンテナからバックエンドに接続できるように、サービスを定義します。

$ kubectl create -f config/backend-service.yaml 
service "backend-service" created

$ kubectl get services
NAME              CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
backend-service   10.19.248.188   <none>        8081/TCP   6s
kubernetes        10.19.240.1     <none>        443/TCP    19m

上記の「CLUSTER-IP」がコンテナにアクセスするための代表IPになります。この例では、「10.19.248.188」が割り当てられています。他のコンテナからこのIPにアクセスすると、自動的に複数のバックエンドコンテナへの負荷分散が行われます。また、コンテナ内部では専用のDNSが用意されており、「backend-service.default.svc.cluster.local」というホスト名にアクセスすると、先ほどの代表IPへのアクセスが行われます。

フロントエンドのデプロイ

デプロイ設定「config/frontend-deployment.yaml」を開いて、コンテナイメージ名に含まれる<PROJECT ID>の部分を実際に使用するプロジェクトIDを修正します。

    spec:
      containers:
      - image: gcr.io/<PROJECT ID>/frontend:v1.0 <-- ココ
        name: frontend-node
        ports:
        - containerPort: 8080

デプロイ設定を指定して、コンテナをデプロイします。下記のように、3個のPodがRunningになれば成功です。

$ kubectl create -f config/frontend-deployment.yaml 
deployment "frontend-node" created

$ kubectl get pods
NAME                             READY     STATUS    RESTARTS   AGE
backend-node-3459171109-09yzl    1/1       Running   0          6m
backend-node-3459171109-2gs4u    1/1       Running   0          6m
backend-node-3459171109-tmkjl    1/1       Running   0          6m
frontend-node-3555902700-el65p   1/1       Running   0          7s
frontend-node-3555902700-irc2x   1/1       Running   0          7s
frontend-node-3555902700-jvfkm   1/1       Running   0          7s

外部からフロントエンドにアクセスできるように、サービスを定義します。

$ kubectl create -f config/frontend-service.yaml 
service "frontend-service" created

こちらのサービス定義ファイルには、「type: LoadBalancer」という指定があります。これによって、GCPのGlobal Load Balancerが自動的に構成されて、ロードバランサー経由でフロントエンドにアクセスできるようになります。しばらく待つと、下記のように「EXTERNAL-IP」がセットされて、これが外部からアクセスする際のIPアドレスになります。(外部アクセス用のIPアドレスは自動的に割り当てるのではなく、事前に確保しておいたIPを明示的に指定することもできます。)

$ kubectl get services
NAME               CLUSTER-IP      EXTERNAL-IP      PORT(S)    AGE
backend-service    10.19.248.188   <none>           8081/TCP   10m
frontend-service   10.19.242.12    104.154.52.102   80/TCP     2m
kubernetes         10.19.240.1     <none>           443/TCP    30m

クライアント側では、このIPアドレスを指定することで、ゲームを遊べるようになります。次は、ゲームIDを指定して、先ほどの続きを遊ぶ例です。

$ API_URL=http://104.154.52.102/api/v1 client/client.py 
Welcome to the five-stone game.
Game ID (0:new game)? 5649391675244544
Your game ID is 5649391675244544
  0 1 2 3 4 5 6 7 8 9
0 - - - - - - - - - - 
1 - - - - - - - - x - 
2 - - - - - - - - - - 
3 - - - - - - - - - - 
4 - - - - - - - - - - 
5 - - - - o o - - - - 
6 - - - - - - - - - - 
7 - - - - - - - - - - 
8 - - - - - - - - x - 
9 - - - - - - - - - - 
(q:quit) x(0-9), y(0-9)? 4,4

  0 1 2 3 4 5 6 7 8 9
0 - - - - - - - - - - 
1 - - - - - - - - x - 
2 - - - - - - - - - - 
3 - - - - - - - - - - 
4 - - - - o - - - - - 
5 - - - - o o - - - - 
6 - - x - - - - - - - 
7 - - - - - - - - - - 
8 - - - - - - - - x - 
9 - - - - - - - - - - 
(q:quit) x(0-9), y(0-9)? 

ただし、バックエンドのAIは相変わらずダミーです。。。。

バックエンドのアップデート

ここで、先ほどのクライアントを停止せずに、こっそりとバックエンドをアップデートします。Cloud Shellの画面の上にある「+」ボタンでシェル画面を新しく開いたら、次のコマンドを実行します。

$ kubectl edit deployment/backend-node

バックエンドのデプロイメント設定がエディターで開くので、イメージのタグ名を「v1.1」に修正して保存します。

    spec:
      containers:
      - image: gcr.io/<PROJECT ID>/backend:v1.1 <--ココ
        imagePullPolicy: IfNotPresent
        name: backend-node

次のように、バックエンドのPodが再デプロイされていることがわかります。

$ kubectl get pods
NAME                             READY     STATUS              RESTARTS   AGE
backend-node-3540566822-0ukin    0/1       ContainerCreating   0          13s
backend-node-3540566822-2n0u3    1/1       Running             0          13s
backend-node-3540566822-hsxys    1/1       Running             0          11s
frontend-node-3555902700-el65p   1/1       Running             0          13m
frontend-node-3555902700-irc2x   1/1       Running             0          13m
frontend-node-3555902700-jvfkm   1/1       Running             0          13m

デプロイ設定のヒストリーを確認すると、新しいイメージのPodを起動した後に、古いイメージのPodを停止するという操作が順番に行われたことがわかります。

$ kubectl describe deployment/backend-node
Name:                   backend-node
Namespace:              default
CreationTimestamp:      Wed, 10 Aug 2016 14:54:53 +0900
Labels:                 name=backend-node
Selector:               name=backend-node
Replicas:               3 updated | 3 total | 3 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  1 max unavailable, 1 max surge
OldReplicaSets:         <none>
NewReplicaSet:          backend-node-3540566822 (3/3 replicas created)
Events:
  FirstSeen     LastSeen        Count   From                            SubobjectPath   Type            Reason   Message
  ---------     --------        -----   ----                            -------------   --------        ------   -------
  19m           19m             1       {deployment-controller }                        Normal          ScalingReplicaSet Scaled up replica set backend-node-3459171109 to 3
  36s           36s             1       {deployment-controller }                        Normal          ScalingReplicaSet Scaled up replica set backend-node-3540566822 to 1
  36s           36s             1       {deployment-controller }                        Normal          ScalingReplicaSet Scaled down replica set backend-node-3459171109 to 2
  36s           36s             1       {deployment-controller }                        Normal          ScalingReplicaSet Scaled up replica set backend-node-3540566822 to 2
  34s           34s             1       {deployment-controller }                        Normal          ScalingReplicaSet Scaled down replica set backend-node-3459171109 to 1
  34s           34s             1       {deployment-controller }                        Normal          ScalingReplicaSet Scaled up replica set backend-node-3540566822 to 3
  33s           33s             1       {deployment-controller }                        Normal          ScalingReplicaSet Scaled down replica set backend-node-3459171109 to 0

クライアントの画面に戻ってゲームを続けると、コンピューターがまともな手を打つように変わっています。やったー。

  0 1 2 3 4 5 6 7 8 9
0 - - - - - - - - - - 
1 - - - - - - - - x - 
2 - - - - - - - - - - 
3 - - - - - - - - - - 
4 - - - - o - - - - - 
5 - - - - o o - - - - 
6 - - x - - - - - - - 
7 - - - - - - - - - - 
8 - - - - - - - - x - 
9 - - - - - - - - - - 
(q:quit) x(0-9), y(0-9)? 4,6

  0 1 2 3 4 5 6 7 8 9
0 - - - - - - - - - - 
1 - - - - - - - - x - 
2 - - - - - - - - - - 
3 - - - - - - - - - - 
4 - - - - o - - - - - 
5 - - - - o o - - - - 
6 - - x - o - - - - - 
7 - - - - x - - - - - 
8 - - - - - - - - x - 
9 - - - - - - - - - - 
(q:quit) x(0-9), y(0-9)? 4,3

  0 1 2 3 4 5 6 7 8 9
0 - - - - - - - - - - 
1 - - - - - - - - x - 
2 - - - - x - - - - - 
3 - - - - o - - - - - 
4 - - - - o - - - - - 
5 - - - - o o - - - - 
6 - - x - o - - - - - 
7 - - - - x - - - - - 
8 - - - - - - - - x - 
9 - - - - - - - - - - 
(q:quit) x(0-9), y(0-9)? 

後片付け

サービスとデプロイを削除します。サービスを削除したタイミングで、Global Load Balancerの設定も破棄されます。

$ kubectl delete service frontend-service
$ kubectl delete service backend-service
$ kubectl delete deployment frontend-node
$ kubectl delete deployment backend-node

この後は、Cloud Consoleからコンテナクラスターの削除、Cloud Storageのプライベートレジストリー用バケットの削除、Cloud Datastoreに保存されたEntity(「GameBoards」というKind)の削除などを行います。

「TensorFlowで学ぶディープラーニング入門 ~畳み込みニューラルネットワーク徹底解説~」が発売されます

TensorFlowで学ぶディープラーニング入門 ~畳み込みニューラルネットワーク徹底解説~

TensorFlowで学ぶディープラーニング入門 ~畳み込みニューラルネットワーク徹底解説~

表題の書籍がマイナビ出版より発売されることになりました。執筆にご協力いただいた方々には、あらためてお礼を申し上げます。販売開始に先立って、「はじめに」を掲載させていただきますので、先行予約される方の参考にしていただければと思います。

はじめに

 ディープラーニングの世界へようこそ! 本書は、機械学習やデータ分析を専門とはしない、一般の方を対象とした書籍です。―― と言っても、ディープラーニングの歴史や人工知能の将来展望を語る啓蒙書ではありません。ディープラーニングの代表とも言える「畳み込みニューラルネットワーク」を例として、その仕組みを根本から理解すること、そして、TensorFlowを用いて実際に動作するコードを作成することが本書の目標です。

 ディープラーニングが世間の話題になりはじめたのは、米グーグル社が「ニューラルネットワークが猫を認識した」と発表したあたりかも知れません。その後、ビデオゲームの操作をDQN(Deep Q-Network)と呼ばれるアルゴリズムが学習したり、さらには、ニューラルネットワークを用いた機械学習システムが囲碁の世界チャンピオンを破るなど、驚くような結果が生み出されてきました。そして、このようなディープラーニングの解説記事で必ず登場するのが、多数のニューロンが何層にも結合された「多層ニューラルネットワーク」の模式図です。このニューラルネットワークの中でいったいなにが起きているのか、ディープラーニングのアルゴリズムはどのような仕組みで学習をしているのか、「何とかしてこれを理解したい!」 ―― そんな気持ちを持ったあなたこそが、本書が対象とする読者です。

 実の所、ディープラーニングの根底にあるのは、古くからある機械学習の仕組みそのものです。簡単な行列計算と微分の基礎がわかっていれば、その仕組みを理解することはそれほど難しくはありません。本書では、手書き文字の認識処理を行う畳み込みニューラルネットワークについて、これを構成する1つひとつのパーツの役割を丁寧に解説していきます。さらに、ディープラーニングの学習処理ライブラリであるTensorFlowを利用して、実際に動作するコードを用いながらそれぞれのパーツの動作原理を確認します。レゴブロックを組み立てるかのように、ネットワークを構成するパーツを増やしていくことで、認識精度が向上する様子が観察できることでしょう。

 ちなみに、TensorFlowの公式Webサイトでは、チュートリアルとしてさまざまなサンプルコードが公開されています。これらのコードを実行してみたものの、コードの中身がよくわからず、自分なりの応用をしようにもどこから手をつけていいのかわからない ―― そんな声を耳にすることもあります。本書を通して、ディープラーニングの根本原理、そして、TensorFlowのコードの書き方を学習すれば、次のステップが見えてくるはずです。ディープラーニングの奥深さ、そして、その面白さを味わうことは、決して専門家だけの特権ではありません。本書によって、知的探究心にあふれる皆さんが、ディープラーニングの世界へと足を踏み入れるきっかけを提供できたとすれば、筆者にとってこの上ない喜びです。