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

仕事中の問題と解決メモ。

最近はPythonとGoogle Cloud Platformがメイン。株式会社ビズオーシャンで企画と開発運用、データ活用とか。 http://mstdn.bizocean.co.jp/@uyamazak https://github.com/uyamazak/

東銀座でビズオーシャン勉強会参加者募集 Dockerとかビッグデータとか

ビズオーシャンで技術力向上と伝える力の強化のために社外の人も交えた勉強会をやりたいと思っています。

希望者はコメントください。

日時

希望者が3人ぐらい集まったら随時開催。平日夕方、夜がやりやすそう。

とりあえず今期中、2018年3月まで

料金

無料

GCP代は各自で。

お題

bizocean本体よりも、新規サービス開発から得た情報が多いです。

1. Docker入門

なぜDockerを使うのかから基本的なコマンドまで。

現在社内の新規開発は、開発環境から本番まで、ほぼすべてDockerを使っています。

2. 予算の少ないビッグデータ基盤oceanusの動かし方

Container Engine(Kubernetes)、BigQuery、HTTP LoadBalancer等GCPを活用して、月3万以下のインフラコストでbizoceanのデータ収集、解析を行っているoceanusを自分の会社で使いたい向け。

ソースコードは下記
GitHub - uyamazak/oceanus: Save all data to Google BigQuery. Fast and row cost using Docker and Kubernetes on GCP

3. MastodonをフルGCPで運用する方法

もはや私用インスタンスと化している下記の建て方を説明します。

mstdn.bizocean.co.jp

4. Google Cloud Datalabを使って機械学習

DatalabというかJupyterの基本的な使い方から、BigQueryのデータから、Pandasのデータフレーム化、skleanのロジスティック回帰などを行う流れとか

現在インターンの大学生に、ブログを書いてもらっています
bizocean-nakayama.hatenablog.com

Django&Python3で一時ディレクトリだけで複数ファイルのZIP圧縮を済ませる

Djangoでフォームで受け取った複数のファイルを、パスワード付きZIPで固めてmodelに保存したかった。

Pythonの標準モジュールzipfileではpass付きを展開はできても、作成はできないのでpyminizipを使う。

ファイル名と、中身を別々に渡せれば一時ファイルでもいいんだけど、pyminizipにはファイルパスのリストを渡す必要がある。


その際、一時ファイルを使ってしまうと、ファイル名がランダム文字列になってしまうので、フォームから送られてきたファイル名を維持するために一時ディレクトリを使い、その中にアップロードされた名前でファイルを作成する。

with文を使ってるので、closeとかの処理が省けるし、ブロックを抜けた時にすぐ消えてくれるので気持ちがいい。


コードは実コードをサンプル用に手を入れてるので動かなさそう。


views.py

import os
import pyminizip
from tempfile import TemporaryDirectory
from .models import ZipFile


def index(request):
    if request.method == 'POST':
        form = MailForm(request.POST, request.FILES)
        if form.is_valid():
            # djangoのフォームからファイル取り出す
            received_files = request.FILES.getlist('file_field')
            password = "pass"
            temp_file_list = []
            with TemporaryDirectory() as temp_dir:
                # ZIPファイルの名前には最初のファイルの名前を拡張子zipにしてつかう
                zip_path = os.path.join(temp_dir, os.path.splitext(received_files[0].name)[0] + '.zip')
                for f in received_files:
                    # pyminizipに渡す時に一時ファイルのままだと、名前がランダムな感じで良くないので
                    # request.FILESから取り出すして使う
                    temp_path = os.path.join(temp_dir, f.name)
                    with open(temp_path, "wb") as tmp_f:
                        tmp_f.write(f.read())
                    temp_file_list.append(temp_path)
                pyminizip.compress_multiple(temp_file_list,
                                            zip_path,
                                            password, 2)
                temp_zip = File(open(zip_path, "rb"))

        # djangoのmodel
        zipfile = ZipFile()
        # zip_pathのままにしてしまうと、/tmp/xxxの階層つきでZIPに入ってしまうのでファイル名だけ渡す
        zipfile.file_field.save(os.path.basename(zip_path), temp_zip)
        zipfile.save()

Django1.10 QUICKSTART-BOOK with Python3: 作りながら学ぶDjangoアプリケーション開発

Django1.10 QUICKSTART-BOOK with Python3: 作りながら学ぶDjangoアプリケーション開発

Django×Python (LLフレームワークBOOKS)

Django×Python (LLフレームワークBOOKS)

1日で理解するDjango超基礎入門

1日で理解するDjango超基礎入門

mastodonのアップデート時にrails assets:precompileが、Compiling webpacker assets で止まる

フルGCPで運用している社内用インスタンス( http://mstdn.bizocean.co.jp )でオリジナルイラストができたのでアップデートしようとしたら、

Binary found at /mastodon/node_modules/node-sass/vendor/linux_musl-x64-48/binding.node
Testing binary
Binary is fine
node-sass@4.5.2 /mastodon/node_modules/node-sass
Done in 37.82s.
Webpacker is installed 🎉 
Using /mastodon/config/webpack/paths.yml file for setting up webpack paths
Compiling webpacker assets 

で止まってしまった。

検索したらどうやらメモリ不足らしい

Rebalance Allocation fails when compiling assets · Issue #2883 · tootsuite/mastodon · GitHub


そういえば先週サーバー代をケチるために、n1-standardからsmallにしたのを思い出した。その時は問題なく終わっていた。

たかが静的ファイルの準備ごときでサーバーをスケールアップさせる必要があることに苛立つ。smallといえども1.7GBあるのに。RailsのせいなのかRubyのせいなのか。

せっかくDockerのコンテナを使って、少ないリソースでたくさんの仮想マシンを動かすことができたのに、中身がメモリを喰うのでは話にならない。

oceanusで使っているコンテナとくらべて、GKE上でのコンテナの立ち上がりも遅いので、デプロイや開発スピードにも影響がでる。

PythonDjangoでは管理系コマンドで待たされたり、メモリ不足になったことはないので、個人的にRubyRailsはやっぱり重い、という印象をmastodonの運用で上書きすることとなった。

Google App Engine + Django +Cloud SQLで社内用短縮URLサービスを作った

データ基盤のoceanusで、Google App Engineを使った短縮URLサービスを追加した。

脳内でもんもんと設計をし、手を動かし始めてから完成まで3日ぐらい。

ソースコードは下記。

github.com

公開しているけど、短縮URLの作成画面は社内の人間のみで、ログイン必須。

作成すると下記のような、大文字小文字英数6字のURLが作られる。


https://s.bizocean.jp/Neas1j


画面はDjangoの管理画面を除くと2画面。


単発で作るフォームと

f:id:uyamazak:20170515141021p:plain

メールマガジンの文面などのURLを一括で変換&置換するもの

f:id:uyamazak:20170515141048p:plain

独自の短縮URLサービスが必要な理由

なぜ、巷にはたくさんの短縮URLサービスがあるのに、oceanus専用の短縮URLが必要かというと、メールマガジンなどに貼るリンクを誰が踏んだかのデータも一元管理したかったから。

ただ他のURLに飛ばすだけでなく、リダイレクト前にoceanusのAPIにデータを送っている。

一部のパラメーターは追加が可能で、例えばユーザーIDが12345のユーザーには下記のようにして送れば、特定が可能になる。

https://s.bizocean.jp/Neas1j?uid=12345

短縮URLアルゴリズム

短縮URLサービスはシンプルなサービスゆえに、いろいろと作り方がWEB上で見つかる。

今回はDjango短縮URLを作るチュートリアルが見つかり、URLの作成部分、HTML部分はかなり流用させていただいた。


Django Tutorial – Building URL Shortener with Djangohellocoding.wordpress.com

Google App Engineを使う理由

下記の理由から、他で使っているGoogle Container Engine(GKE)ではなく、GAEのSTANDARD ENVIRONMENTを利用した。

  1. メールマガジンに貼ったりするため、アクセスの偏りが大きい
  2. シンプルなサービスなので必要なライブラリが少ない
  3. 今後の機能拡張も必要ない

1に関して、bizoceanでは200万以上の会員にメルマガを配信しているので、配信直後にアクセスが集中する。GKEでもオートスケールはできるが、MAXがどのくらい必要なのかとか、POD単位、クラスタ単位なのかを考えたり、指標をどうするかなど考えたり設定することが多い。でもGAEであればいい感じに勝手にスケールしてくれるので心配が少ない。

2に関して、今回はDjangoMySQLが使えれば十分なことがわかっていたので、pipなど外部のライブラリの追加も必要なかった。正確にはpythonのrequestsが使いたかったが、面倒だったのでurllibとか標準ライブラリで済ませた。Python3を使いたいとか、大きめのライブラリを使いたい、RedisとかRabbitMQが必須、となればGKEを使ってたと思う。FLEXIBLE ENVIRONMENTであればPython3.4が使えるけど、中途半端だし、Dockerイメージ使うのであればGKEでいいじゃんという気になる。

3は、どんどん拡張していく予定であれば、2の理由等から難しいけど、変換とリダイレクト、ユーザー認証だけ、と決まっていたのでGAEで十分だった。


また現在、oceanusのWEB APIであるarmsはフレームワークFalconを使っており、こちらはCythonが必要だし、シンプルなフレームワークなため、Djangoの用にユーザー認証や管理画面を作るのは面倒だったというのもある。

Djangoを使う理由

個人的にDjangoの経験があったことと、ドキュメントが充実していること、ユーザー認証を簡単に実装できること。

下記の公式チュートリアルを動かせることができれば、あとは継ぎ足しですぐ完成できる。

Running Django on App Engine Standard Environment  |  Python  |  Google Cloud Platform

GAEでDjangoを動かす、公式ドキュメントがあるし、上記の短縮URLみたいにWEB上にDjango関係のドキュメントは非常に充実している。

問題としてはちょっとバージョンが古い(現在1.11があるけど1.9まで)ことぐらい。



ユーザー認証が必要で、シンプルで、実質無限なスケールを必要なサービスには、手軽なGAE+Djangoがおすすめです。

Django1.10 QUICKSTART-BOOK with Python3: 作りながら学ぶDjangoアプリケーション開発

Django1.10 QUICKSTART-BOOK with Python3: 作りながら学ぶDjangoアプリケーション開発

Mastodonで/public以下の静的ファイルの画像やjs等が表示されない時

GCPで動かしている自社社員用インスタンス(
https://mstdn.bizocean.co.jp
)のバージョンアップ時にはまった
nginx等を使わずrailsのサーバーで画像も返すときは、下記の環境変数を追加する必要があった。

RAILS_SERVE_STATIC_FILES "true"

railsのサーバーで画像等を返すのは重いので、ロードバランサー側でキャッシュしたほうが良さげかな

gcloudコマンドで複数の設定の切り替え

いろいろ作ってると、GCPのプロジェクトが増えてきて、切り替えとかが面倒になる。

gcloud init

を打つと、設定ファイル(configuration)が出てきて、切り替えと再認証(Switch to and re-initialize existing configuration)は出来るけど、切り替えだけの方法がわからなくて嵌ったのでメモ。


すでに使っている環境でgcloudを打つと下記のように出てくる。

% gcloud init
Welcome! This command will take you through the configuration of gcloud.

Settings from your current configuration [gae] are:
[compute]
region = asia-northeast1
zone = asia-northeast1-a
[core]
account = yu_yamazaki@example.com
disable_usage_reporting = False
project = oceanus-example

Your active configuration is: [gae]

Pick configuration to use:
 [1] Re-initialize this configuration [gae] with new settings
 [2] Create a new configuration
 [3] Switch to and re-initialize existing configuration: [default]
 [4] Switch to and re-initialize existing configuration: [sample]

新しく設定ファイルを作りたいときは[2] Create a new configurationを選択して、言われたとおりにいろいろやる。

今回はすでにある設定sampleに切り替えたいので、下記を打つと既存のものにできる。

% gcloud config configurations activate sample

コマンドが長過ぎる。

google compute engine - How to change the active configuration profile in gcloud? - Stack Overflow

面倒なのでシェルのエイリアスに追加する。私の場合はzshなので

% vim ~/.zshrc

#追加
alias gchange='gcloud config configurations activate'

#読み込み
% source ~/.zshrc

で、

gchange 設定名

で切り替えできるようになった。

最初gswitchにしたけど、長いしつづりが直感的じゃないので、gchangeにした。


現在どんな設定があるかとか、削除の方法とかは下記で出てくる

% gcloud config configurations
ERROR: (gcloud.config.configurations) too few arguments
Usage: gcloud config configurations [optional flags]
command may be activate | create | delete | describe | list

For detailed information on this command and its flags, run:
gcloud config configurations --help

Google Container Engineで自社用Mastodon立てた

いろいろと勉強になりそうなので勢いで立てた。詳細は今度記事にする。

Mastodonを構築、運用できてるってだけである程度の技術アピールになる気もするので、エンジニアがいる会社のブランディングなんかにも使えるのではないかと思った。

mstdn.bizocean.co.jp

最初ローカルのDocker環境で動かすのはdocker-composeを使って半日もかからなかったけど、公開ドメインがないとリモートフォローができないので必要になった。



いろいろとクラウド化周りで1日半ぐらいかかってしまった。

アカウントはこれ
uyamazak - Mastodon@bizocean.co.jp


ざっくり構成としては

  • Google Container Engine
  • HTTPS LoadBalancer + Let's Encryptの証明書
  • Cloud Storage(画像ファイルとか)
  • Cloud SQL (ベータのPostgres)

最初マシンタイプをケチったらPODが立ち上がらなくなったので、スタンダード2台も使ってしまっている。


Cloud SQLも最低が4GBのマシンだったからオーバースペック気味。
月5000円くらいか。

いくつかトピックを。

登録できるメールアドレスを制限する

違法画像がとか、いろいろと不安もあるので、自社社員専用に会社のメールアドレスがないと登録できないようにする。

環境変数

EMAIL_DOMAIN_WHITELIST

で指定できる。"bizocean.co.jp"を指定した。

ブラックリスト

EMAIL_DOMAIN_BLACKLIST

もある。
ソースコード上はここ
https://github.com/tootsuite/mastodon/blob/master/config/initializers/blacklists.rb

ストリーミングを同ドメイン

ストリーミング用サーバーを別ドメインにしているところが多いけど、ドメインが別だと証明書が面倒なので、ロードバランサーでパス指定でバックエンドサービスの切り替えをして、同じドメインで動くようにしてある。

ただバックエンドごとにポートを変えるにはノードプールも分ける必要があったので、ノードプールを2つ使っている。

うまく説明できないので、ロードバランサー設定をキャプチャ↓

f:id:uyamazak:20170427110055p:plain

ユーザーの画像等をクラウドストレージに

下記記事を参考にそのままできた

qiita.com

/mastodon/public/assetsと/mastodon/public/systemをちゃんとマウントしておかないとロゴとかの画像が表示されない

これでしばらくハマった。
上記の設定で画像はクラウドストレージにできたと思ってたら、サイトのシステムで使う画像は別だった。
2つを永続ディスクでマウントして、bundle exec rails assets:precompileを実行、さらにコンテナ再起動が必須なもよう。

cronの設定

認証の更新なんかで1日1回必要なコマンドがある。
現在、KubernetesのCronJobで出来ないか調べてる。

操作は基本Cloud Shell

主にkubectlとか。
なんとかなる。
社内サーバーは他プロジェクトで使ってるので、いろいろと切り替えが面倒だった。

ただ、Cloud Shellを実行しているコンテナが裏で変更されているのか、頻繁にgcloud周りの認証が切れてしまうので下記のようなshスクリプトを置いて、すぐ実行できるようにした。

gcloud config set compute/zone asia-northeast1-b
gcloud config set container/cluster bizocean-mstdn
gcloud container clusters get-credentials bizocean-mstdn

Cloud SQLへの接続はプロキシで

Google Container Engine から接続する  |  Cloud SQL ドキュメント  |  Google Cloud Platform


ちょっと面倒だけど、セキュリティ上IPで接続せずにこうした方がいいらしい。

一つのDeployment上に複数コンテナを動かしたり、KubernetesのSecurityを使うのが始めてだったので勉強になった。

上の説明だと設定ファイルの全体が出てこないけど、下記レポジトリに入ってた。
container-engine-samples/cloudsql at master · GoogleCloudPlatform/container-engine-samples · GitHub

ロゴとか、トップの画像を変える

まだやってないけど、mastodonのレポジトリを取ってきて

app/assets/images/

内の画像を差し替えれば行けそう。


質問は下記まで。

mstdn.bizocean.co.jp