アプリとサービスのすすめ

アプリやIT系のサービスを中心に書いていきます。たまに副業やビジネス関係の情報なども気ままにつづります

kubenetesでDjangoのアプリをデプロイする手順と作業ログ(on GKE)

pythonフレームワークでflaskが人気みたいだけど、せっかくDjango勉強したし、フレームワーク的に仕組みも同じなので、どうせならDjangoで作ったアプリをGKE上でデプロイしようと思い、やってみた。

前回、ローカルで画像の予測ラベルを表示するDjangoのアプリを作ったので、それを使用。

これに、redisとceleryのworker処理を加えた。ローカルで動かすとredis 用、worker用、Django用に3つのターミナルを使って処理を行う。

f:id:trafalbad:20190216004311p:plain

ローカルではちゃんとworker処理が行われて予測ラベルが表示される。worker部分は本題でないので詳細はコードを参照。

やったことはdjango_redisなるライブラリを使って、だいたい
このサイト


celeryチュートリアル
を参考にして作った。


けど、worker処理ありのDjangoをGKE上でデプロイしようとすると、redis用のpodを作るあたりでうまくいかない。

なのでworker処理なしの、前作った普通の画像ラベル予測用のDjangoアプリをGKE上でデプロイすることにした。


GCPでのDjangoデプロイの全体像
f:id:trafalbad:20190330134444j:plain


そのデプロイ手順と作業ログを書いていく。

目次
1.DjangoをGKE上でデプロイする

2.workerジョブのDjangoをGKE上でデプロイしようとした時の作業ログ


f:id:trafalbad:20190215203319j:plain

1.DjangoをGKE上でデプロイする

worker処理のない普通のDjangoアプリをデプロイ

worker処理を行うときは、フロントエンドに直結するファイルのコードに、worker処理用のdelayメソッドを使う。
今回、フロントエンドに直結するのはmain.pyというファイル。worker処理ありとなしのコードは以下の通り。

・main.py(worker処理なし)のdelayメソッド部分

from image_pred.tasks import add_numbers   # workerから関数の呼び出し

def pred(img_path):
    x=np.asarray(Image.open(img_path))
    x=np.resize(x,(150,150,3))
    x = np.expand_dims(x, axis=0)
    image = preprocess_input(x)
    image_dump=base64.b64encode(image.dumps()).decode()
    id = add_numbers(image_dump)
    return id

・main.py(worker処理あり)のdelayメソッド部分

from image_pred.tasks import add_numbers # workerから関数の呼び出し
def pred(img_path):
    x=np.asarray(Image.open(img_path))
    x=np.resize(x,(150,150,3))
    x = np.expand_dims(x, axis=0)
    image = preprocess_input(x)
    image_dump=base64.b64encode(image.dumps()).decode()
    id = add_numbers.delay(image_dump)
    time.sleep(70) # 70秒待つ
    ids=id.result
    return ids


delayメソッドを使うか、使わないかだけで、他のworker用のceleryファイルはそのままにしてある。



GKE上でデプロイするためにローカル用のDjangoの変更点

GKE上でデプロイするにはGoogleのチュートリアルを参考にして、足りないところをDjangoアプリのサンプルコードを使って改良した。

これをもとに自分のDjangoのコードで変更したところを列挙してく。

Djangoのproject名:image_pred(チュートリアルの場合、mysite)
アプリ名:myapp(チュートリアルではpolls)

・DockefileのCMDコマンドを変更

CMD gunicorn -b :$PORT image_pred.wsgi



・polls.yamlの内容を変更

・metadata.name
・metadata.labels.app
・spec.metadata.app

の三ヶ所をpollsからmyappに変更



・必要ファイルの必要箇所を追記・変更

・polls.admin.py

・mysite.urls.py

・mysite.settings.py

の内容を追記変更



・myapp/migrations/0001_initial.pyを追加 & 内容変更

githubのpolls/migrations/0001_initial.pyと同じように、0001_initial.pyのmygrations.AddField()内の以下の部分を変更

"to='polls.Question')"
# 以下に変更(pollsを自分のアプリ名に変更する)。

"to='myapp.Question')"


このファイルがないと

$python manage.py makemigrations

実行時にうまくいかない。


他は足りないファイルを追加したりした。


GKE上でデプロイする手順

あとはGKE上でデプロイするための手順を書いてく。
リージョン:us-central1 、ゾーン:us-central1-c、インスタンス:myapp-bccプロジェクト名:sturdy-willow-167902、connectionName の値:sturdy-willow-167902:us-central1:myapp-bcc

1.ローカルにDjango アプリを作成 (at local)

2.ローカル環境の設定

# SQLプロキシをインストールする at local(macos64ビット)
$ curl -o cloud_sql_proxy https://dl.google.com/cloudsql/cloud_sql_proxy.darwin.amd64 && chmod +x cloud_sql_proxy

# Cloud SQL(PostgreSQL) インスタンスを作成する at GKE
"""
SQL→インスタンス作成→PostgreSQL選択→インスタンスID myapp-bcc パスワードk8s リージョン us-central1 設定オプションから[マシンタイプとストレージの設定]でcpu(コア数) 2とメモリ→[作成]
"""

# connectionNameの確認
"""
「SQL」をクリック、インスタンスの一覧が表示されるので、使用するインスタンスをクリック、概要の下にある「インスタンス接続名」があるので確認(sturdy-willow-167902:us-central1:myapp-bcc)
"""

# 新しい Cloud SQL ユーザーとデータベースの作成 at GKE
・DB(gcloud sql databases create [DATABASE_NAME] ―instance=[INSTANCE_NAME])
$ gcloud sql databases create redis --instance=myapp-bcc

・DBユーザーを作成する(by GCU)
"""
GCPメイン画面で、[SQL]を選択、データベースを作成したいインスタンスをクリック、ユーザー」タブをクリック、「ユーザーアカウントを作成」をクリック、「ユーザー名」(bc)と「パスワード」(k8s)を入力して「作成」
"""

3.サービス アカウント(ユーザー & パスワード)を作成する at GKE

「IAMと管理」の中から「サービスアカウント」を選択、上部の「サービスアカウントを作成」をクリック


1.サービスアカウント名(bc)
2.役割に「Cloud SQL クライアント」を設定して「続行」をクリック
3.「キーを作成」をクリックし、キーのタイプにて「JSON」を選択して「作成」をクリックと認証ファイルがダウンロードされる
4.キーが表示されていることを確認して、「完了」をクリック
5. サービスアカウントの一覧に戻るので、作成したアカウントが存在することを確認する
6.JSONキーをmanage.pyと同じパスに移動させる

# Cloud SQL インスタンスを初期化する at local
# プロキシの開始コマンド
$ ./cloud_sql_proxy -instances=インスタンス接続名=tcp:5432 -credential_file=key.json

# 例
$ ./cloud_sql_proxy -instances=sturdy-willow-167902:us-central1:myapp-bcc=tcp:5432 -credential_file=sturdy-willow-167902-beaf39d3bdbd.json


4.データベースを構成する at local

# ローカルでDBにアクセスするため環境変数を設定(DBユーザー名作成時の「ユーザー名」(bc)と「パスワード」(k8s)を使う)
$ export DATABASE_USER=bc && export DATABASE_PASSWORD=k8s

5.GKE を構成する at local

# マニフェストファイルpolls.yaml で、以下のところを変更
your-project-id =>実際のプロジェクトID(プロジェクトID:sturdy-willow-167902)

your-cloudsql-connection-string => connectionName の値: sturdy-willow-167902:myapp-bcc


6.ローカルコンピュータでアプリを実行する

# 孤立した Python 環境を作成し、依存関係をインストール(at local)
$ brew update && brew install mysql   # mysqlをインストール
$ virtualenv venv && source venv/bin/activate && pip install -r requirements.txt


# モデルを設定するために Django の移行を実行 at GKE
"""
=>Cloudsql APIを有効にする
APIとサービス -> ダッシュボード -> APIを有効にする -> 「cloud sql api」 で検索 -> 有効にするをクリック

=> ローカルのDjangoファイルのmysite.settings.pyのDATABASEのNAMEをredisに変更 at local
"""

# celery用のデータベースマイグレーション 
$ python manage.py migrate django_celery_results

# マイグレーションファイルを作成 & 実行(データベースの同期)
$ python manage.py makemigrations && python manage.py migrate 

# 試しにローカル ウェブサーバーを起動
$ python manage.py runserver  # 「http://127.0.0.1:8000/myapp/」 にアクセス & Ctrl+Cキーで停止

参考
Cloudsql APIを有効にする
django_celery_results用のマイグレーション



7.Django 管理コンソールを使用する at local

# スーパーユーザー(管理画面を利用できる管理者ユーザー)の作成
$ python manage.py createsuperuser

# 実行
$ python manage.py runserver
# 「http://127.0.0.1:8000/admin/」にアクセス

# UsernameとPasswordを入力し、管理サイトにログイン

8.アプリを GKE にデプロイする at GKE

# Cloud Storage バケットを作成 & 公開
$ gsutil mb gs://polls-images && gsutil defacl set public-read gs://polls-images

# => gcloudコマンドインストールし、ローカルで実行 (gsutil rsync -R static/ gs://<your-gcs-bucket>/static)&  staticフォルダにあるmyappに移動
$ cd myapp
$ gsutil rsync -R static/ gs://polls-images/static

# mysite/settings.py で、STATIC_URL の値をこの URL「http://storage.googleapis.com/<your-gcs-bucket>/static/」に設定
=http://storage.googleapis.com/polls-images/static/

# GKE クラスタを作成 at GKE
$ gcloud container clusters create myapp --scopes "https://www.googleapis.com/auth/userinfo.email","cloud-platform" --num-nodes 4 --zone "us-central1-c"


# GKEクラスタ操作権限get(kubectl が正しいクラスタとやり取りするように構成されていることを確認)
$ gcloud container clusters get-credentials myapp --zone "us-central1-c"


# シークレットの作成=> JSONキーアップロード & アクセス用にシークレットを作成
$ kubectl create secret generic cloudsql-oauth-credentials --from-file=credentials.json=sturdy-willow-167902-beaf39d3bdbd.json

# データベース アクセスに必要なシークレット作成
$ kubectl create secret generic cloudsql --from-literal=username=bc --from-literal=password=k8s

# configmap作成
$ kubectl create configmap redishost --from-literal=REDISHOST=10.0.0.3
# configmap確認
$ kubectl get configmaps redishost -o yaml


# Cloud SQL プロキシのパブリック Docker イメージを取得 at GKE
$ docker pull b.gcr.io/cloudsql-docker/gce-proxy:1.05

# From here command run at local

# Docker イメージを作成(each Dockerfileを移動)
$ docker build -t gcr.io/sturdy-willow-167902/myapp .

# 認証ヘルパーとしてgcloud を使用するようにdockerを構成。GCRにイメージを pushできる
$ gcloud auth configure-docker

# docker push
$ docker push gcr.io/sturdy-willow-167902/myapp

# GKE上でpod.yamlをアップロード at GKE
$ kubectl create -f myapp.yaml


# 動作確認 at GKE
$ kubectl get pods or $ kubectl describe pod <your-pod-id>
$ kubectl get services myapp

# 'http://[EXTERNAL-IP]/myapp/'にアクセス。

ちゃんとローカルと同じくデプロイできた。

f:id:trafalbad:20190216133734p:plain


2.worker処理ありのDjangoをGKE上でデプロイしようとした時の作業ログ

worker処理ありのDjangoアプリ(ローカルでworker(celery)、redis、Django用にターミナルを3つ使って行うやつ)をGKE上でデプロイしようと思ったけど、GKE上でredisへのアクセスがうまくいかないため、「GKE上でredisを使うためにやった作業」のログ。

ちなみにcelery用ファイルはimage_predフォルダ内に入れてあるので、celeryのworker起動用のファイルは以下の通り。

celery.yamlcelery_docker/Dockefile、run.sh、celery.py


実験1.Dockerfileでapt-getでredis-serverインストール & CMDで起動


エラー1:なぜか、レスポンス返ってこない。

エラー2:Dockefile一つにCMDコマンドでredis-server、celery、Django3つ一気に起動するのはNG。
多分、ローカルでターミナル3つ必要だったから、Dockerでも一気に3つ起動は無理だからだと思う。



実験2.redisインスタンスを使用する(k8sクラスタはipエイリアスとそうじゃないの両方試した)


Googleチュートリアル通りにやった前の記事を参考に、redisインスタンスを設定し、「redis://10.0.0.3:6379」にアクセス。

エラー1:IPの範囲設定を前の記事通りすると、サーバにアクセスできない

エラー2:IP範囲設定せずに、configmap使ったらアクセスできたものの、結果が返ってこない。おそらくredisインスタンスにアクセスできてない



実験3.redisクラスターの使用


以下の手順でredisクラスターを作成

# githubからclone
$ git clone https://github.com/sanderploegsma/redis-cluster

# GCPにファイルアップロードし、起動
$ kubectl apply -f redis-cluster.yaml

# 下記コマンド実行してredisクラスターを作成
$ git clone https://github.com/antirez/redis.git && cd redis && make && sudo make install
$ apt-get update && apt-get install ruby vim wget redis-tools && gem install redis && wget http://download.redis.io/redis-stable/src/redis-trib.rb && chmod 755 redis-trib.rb

# podが6つできるので、実行
$ kubectl exec -it redis-cluster-0 -- redis-cli --cluster create --cluster-replicas 1 $(kubectl get pods -l app=redis-cluster -o jsonpath='{range.items[*]}{.status.podIP}:6379 ')

# 本来、マニフェストファイル内では、cluster IPがredisのIPになる

エラー:なぜか今まで発生してないpandasのエラーがでてきて、解決策講じたものの通用しないので中断


この記事だと、一番現実的なのはredisクラスターかなと思った。k8s用のredisクラスターは事例がgithubにたくさんある。

まあ仕組み理解しないとこの問題は解決しないなと感じた。

今回は普通のDjangoアプリケーションをGKE上でデプロイするまでの内容 & 手順と、ちょっとした作業ログのまとめを備忘録もかねて書いた。



個人的メモ

Djangowsgi.pyファイルに関係する「サーバソケット」について

ソケットは一般にクライアントとサーバーの対話で使用される。
クライアントとサーバーとの間のデータ交換は、 クライアントがソケットを経由してサーバーに接続しているときに行われる。



・gunicornとその-bオプション(-b BIND, --bind=BIND )について

Gunicornとは、Python製のWSGIサーバ(WSGI(Web Server Gateway Interface)でサポートされているWebサーバとWebアプリケーションをつなぐサーバ)。

pipでインストールして、

$ gunicorn myproject.wsgi

とすれば、走る。

− bオプションはバインド(結合)するサーバソケットを指定する。

サーバソケット(BIND)は「$(HOST)」、「$(HOST):$(PORT)」 or 「unix:$(PATH)」で指定。IPは有効なものを指定。


・dockerイメージ全削除コマンド

$ docker images -aq | xargs docker rmi


・プロキシサーバについて

Webブラウザ)の代わりに、ホームページにアクセスしてくれるコンピュータ(サーバ)のこと。

f:id:trafalbad:20190325114444p:plain

ここでは、kubenetesとローカルマシンを繋いでくれる中継地点の役割を果たしてる