koboriakira.com

Week38

September 26, 2020

週次レビューを再開したので、ついでにこちらにも書いておく。

今週のアウトプット

PACKERS FUNの更新

今週からスタートしたNFLについて、シーズン全体の記事と試合に関する記事など。先々週とあわせて10記事以上を更新。

AWSチュートリアルを終える

チュートリアルを読みながら、ウェブサイトの構築を行った。

AWSの概要はなんとなく理解したが、CloudFormationのテンプレートを理解していないので実践で使うには勉強が足りなそう。とはいえ人と話すときには困らなくなった。

それよりもAWSは個人で使うにはお金がかかるので、基本無料で使えるGoogle Cloud Platform(GCP)をキャッチアップした。末尾の日記の通り、GCPをつかってアプリを構成することができた。ほぼこれでよさそう。

保育園の見学プランを立てる

大体立てた。ビックリしたのは見学概要がサイトに掲載されてから数時間で予約が埋まってしまったこと。スクレイピングでページ更新を確かめようかどうか迷っている。

子どもの動画を集めなおす

Dropboxと簡単なプログラムを使って、簡単にHDDへ転送するようにした。このうち「使う動画」や「使える時間」をまとめておきたいなと考えている。

いま求められているスキルを調べる

最近の求人情報をちょっと確認。やっぱりAWS系の知識が優遇されている。GCPの知識をつけておいて、クラウド全体の知識として自己紹介できればいいかな。あとはPM経験。

それほど「そんな知識/経験が!」というのはなかったので、いったんはもういいかな。

2020年9月19日、土曜日

Bを連れて散歩。予定通り寝てくれたが、今日は1時間近く寝てくれたことにビックリ。その間に週次レビューを復活させ(上記の通り)、来週の目標を決めた。

そのあとTodoistを自分好みに使うために、簡単なプログラムを作成した。Pythonのライブラリがボランティアで提供されているので、30分もあれば作ることができた。

お昼ころ、はじめて離乳食を食べさせてみた。思ったよりも食べてくれて順調。口の中に入れるだけでも立派だと思う。


これまで撮影した写真や動画の整理について悩む。とりあえず写真や動画を保存するとして、

  • どのようにスマホ(またはデジカメ)の写真をHDDへ移動するか
  • これをどのように流れ作業にするか

というところで色々と考えた。いろいろと試してみた結果、(スマホは)Dropboxに同期させつつ、適当な間隔でこれをゴッソリHDDに移すことにした。

その際、日付ごとに整理したくなったので、Todoist同様にPythonのプログラムをちゃちゃっと書く。これで思いたったときにHDDを接続してプログラムを走らせれば自動的にHDDへ保存ができるようになった。

ここまでいくと、NATを導入して適宜HDDにコピーするようにしたくなってきた。次のセール期間はNAT導入を検討しよう。

みたいなことをやっているうちにもう夜。新日本プロレスG1 CLIMAXの開幕戦を見つつ(オスプレイのヘビー転向は成功したと思う)、パッカーズの直前情報を確認。ブログにして寝る。

2020年9月20日、日曜日

雨を気にして自宅で過ごす。Bはずっと「クマーバチャンネル」を見ている。私の時代とくらべて最近のキッズソングは低音が出ていると思う。

AWSのチュートリアルはDBの追加に進んだ。そのままユーザ認証もチャレンジしてみる。認証メールの本文が味気なくスパムっぽく見えたので、実際に使う場合は修正しないといけなさそう。

配偶者がLINEスタンプを作っているので、「画像をLINEスタンプの形式に変換できれば楽なのにな」と思って調べてみたら、やっぱり既にやっている人がいた。
上記を参考にさせてもらいつつ、クロップ機能も追加してライブラリを作った。スタンプ画像ができあがったら実際に使ってみてもらって上手くいくかテストする予定。

2020年9月21日、月曜日

起床後、Bと一緒にパッカーズの試合を観る。結果は圧勝。最終クォータはBの面倒を見ながら横目で観戦する程度だった。

試合結果のブログも書き終えると昼間だったが、涼しいので散歩に出かける。忘れてたけど今週は4連休で、人出はほとんど元に戻ってきた感じ。休日はふつうに働いている人たちのものだと思うので、なるべく邪魔をしないように散歩して、公園ですこし寝てもらって帰宅。

配偶者がNetflixで江原啓之MCのスピリチュアル番組を見つけた。出演者の中に澤穂希がいて驚いたが、それよりもアシスタントが住吉美紀だったことに震える。

ふと最近の転職事情を知りたくなって、いくつか求人情報を確認してみた。やっぱりAWSが求められてるんだなと実感。あとはリーダー経験とか。そろそろ「経験」でご飯を食べるフェーズに入ってきている。

2020年9月22日、火曜日

起床後、AWSチュートリアルをやる。とりあえず終わった。各サービスのイメージを掴めたかな、という程度。請求がすごいきて驚いた(1日4ドルぐらい)。

感覚はつかんだので、GCPでもやってみようと思う。個人用のPythonのテンプレートを準備したあと、ためしにCloud Functionsを使ってみた。

最初の関数: Python  |  Google Cloud Functions に関するドキュメント

gcloud経由で操作するにはCloud Build APIを利用する必要があるみたいなので、APIを有効にした。

時間があったので、Cloud Firestore を使ってみる  |  FirebaseでFirestoreとの連携も試してみる。とりあえず上手くいった。

わからないのはGCPとFirebaseの関連性について。GCPがFirebaseを包括しているようなイメージだけど、勘違いしていそう。困ったときにちゃんと調べるか。

夜はCloud Runを触ってみる。Cloud Build、Container Repositoryあたりを使ってDockerイメージからCloud Runのサービスを起動できた。いい感じ。

明日以降にCloud RunからCloud Firestoreを触ることができれば(今日作成したコレクションを利用しよう)、簡単にAPIをつくることができそう。フロントエンドはFirebaseかNetlifyでいこうか。

2020年9月23日、水曜日

雨が降るそうだったのでBの散歩は諦める。

一緒に『ロンドンハーツ』を見ていたら、炎上騒ぎのあったシュウペイに対して有吉から「間違った意見に屈するな」の一言。ちょっといいシーンだったし、世代の断絶にも気づくシーンだった。


GCPはCloud RunからFirestoreに接続させることもできた。せっかくなのでFastApiを使ったテンプレートプロジェクトを作ろうと思う。

Firestoreについて調べていたら、「FirebaseのCloud Firestoreを発展させて、最近GCPのCloud Firestoreができた」という旨の記事を読んだ。少し納得。ドキュメントの位置がわからなくなるので、こっちにリンクを張っておこう。

Firestoreのドキュメント

ちなみに現在はDatastoreでなく、Firesotreを利用しているはず。この差もいずれ重要になるかもしれないが、いまは無視しておく。

認証情報まわりでミスった(誤ってキーをレポジトリにアップしてしまっていた)ので、その解決策を検討。

どうやら同じプロジェクト(正確にはサービスアカウントだと思う)なら認証情報の受け渡しが不要みたい。そのため開発環境ではキーを利用して、本番環境はGoogleに任せるようにした。


テレワークが進んだおかげで、育休中も会社の情報が追いやすくなった。

気持ちを切り替えるためにまったく見ないようにしていたが、大きな全社イベントがあったので、その映像を見る。


今日一日でGCPについては割と詳しくなった気がする。明日は時間をつくってAPIサーバの公開をドキュメント化・テンプレート化しよう。

またpackersfun.comをGoogleDomainに移管した。このkoboriakira.comも移管する予定。

2020年9月24日、木曜日

珍しくBが大泣きした。散歩に連れていけれないからだろうか。どちらにせよ活動時間がオーバーしていたので頑張って寝てもらった。

GCPを使ったAPIサーバは、最終的に下の技術を使うことにした

  • FastAPI: PythonのAPIサーバ
  • Docker: コンテナ
  • git: リビジョン管理
  • Google Cloud Run: コンテナのデプロイサービス
  • Google Cloud Builder: CD/CIサービス
  • Google Container Respository: Dockerイメージ管理サービス
  • Google Cloud Firestore: データベース

とりあえずメモ。

0. 前提

Docker、gcloudはインストールしておいてもらう。

$ docker --version
Docker version 19.03.12, build 48a66213fe

$ docker-compose --version
docker-compose version 1.26.2, build eefe0d31
$ gcloud --version
Google Cloud SDK 311.0.0
bq 2.0.60
core 2020.09.18
gsutil 4.53

1. プロジェクトを作成する

my-awesome-apiという名前でプロジェクトを作成した。次のキーを混同しないように。

  • プロジェクト名: my-awesome-api
  • プロジェクトID: my-awesome-api-290501
  • プロジェクト番号: 359948014733

2. gitリポジトリを作成、クローンする

awesomeapiという名前でgitリポジトリを作成した。

mkdir ~/git
cd ~/git
git clone git@github.com:koboriakira/awesomeapi.git
cd awesomeapi

3. Dockerコンテナ内でFastAPIサーバを立てる

Dockerfileをつくって、イメージをbuildして、コンテナをrunする。

FROM python:3.8.5-slim

WORKDIR work

ADD awesomeapi /work/awesomeapi
docker build -t awesomeapi-image .
docker run -v $(pwd)/awesomeapi/:/work/awesomeapi -p 7777:8000 -it --rm --name awesomeapi-container awesomeapi-image /bin/bash

ファイルを作成して、Dockerコンテナ内にもファイルが共有されていることを確認する。

ローカル上にawesomeapi/main.pyを作成。

$ tree
.
├── Dockerfile
├── README.md
└── awesomeapi
    └── main.py
from typing import Optional
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
    return {"Hello": "World"}


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
    return {"item_id": item_id, "q": q}

Dockerコンテナ内で、ファイルを確認。

# pwd
work
# ls awesomeapi
main.pu

Dockerコンテナ内で、必要なPythonライブラリをインストール。

# 先にpip自体のバージョンを最新化
$ pip install --upgrade pip
$ pip install fastapi uvicorn

Dockerコンテナ内で、FastAPIサーバを起動。

$ uvicorn awesomeapi.main:app --host 0.0.0.0 --reload

http://localhost:7777/にアクセスして、Docker内のlocalhost:8000に立てられたFastAPIサーバと疎通できていることを確認。
具体的にはブラウザに{"Hello":"World"}と表示されるはず。これがレスポンス。

ためしにmain.pyread_rootメソッドにある{"Hello":"World"}を編集してブラウザをリロードしてみる。するとレスポンスも変わるはず。

4. Docker ComposeでFastAPIサーバを起動する

コンテナ内に入らずに開発できるようにする。

まず必要なPythonライブラリをrequirements.txtにまとめる。

.
├── Dockerfile
├── README.md
├── awesomeapi
│   ├── __pycache__ (以降は省略)
│   └── main.py
└── requirements.txt
fastapi
uvicorn

次にrequirements.txtをDockerイメージ内に追加してライブラリをインストールするように、Dockerfileに追記する。

FROM python:3.8.5-slim

WORKDIR work

ADD awesomeapi /work/awesomeapi
ADD requirements.txt /work/requirements.txt
RUN pip install --upgrade pip && \
  pip install --no-cache-dir -r requirements.txt

さらにDockerコンテナを起動したときにFastAPIが起動されるように追記する。

FROM python:3.8.5-slim

WORKDIR work

ADD awesomeapi /work/awesomeapi
ADD requirements.txt /work/requirements.txt
RUN pip install --upgrade pip && \
  pip install --no-cache-dir -r requirements.txt

CMD ["uvicorn","awesomeapi.main:app","--host","0.0.0.0","--reload"]

新しいDockerイメージをbuildして、そのイメージからコンテナをrunする。docker runの末尾の/bin/bashがなくなったことに注意

docker build -t awesomeapi-image .
docker run -v $(pwd)/awesomeapi/:/work/awesomeapi -p 7777:8000 --rm --name awesomeapi-container awesomeapi-image

最後に、これらのDockerイメージのbuild・コンテナのrunを簡単にするためにDocker Composeを使う。

次のようなdocker-compose.ymlを作成して、docker-compose upで起動

.
├── Dockerfile
├── README.md
├── awesomeapi
│   └── main.py
├── docker-compose.yml
└── requirements.txt
version: '3'
services:
  app:
    build: .
    ports:
      - '7777:8000'
    volumes:
      - ./awesomeapi:/work/awesomeapi
docker-compose up

同じくlocalhost:7777にアクセスでき、main.pyを編集すれば反映されることを確認。

ここまでで、(データベースを使わない)APIサーバの開発環境を作成することができた。いったんgitリポジトリにコミット。

git add .
git commit -m "Initial Commit"
git push origin master

4. Google Cloud Runにデプロイしてサービスを公開する

Cloud Runに移動して、「サービスを作成」をクリック。

  • リージョン: asia-northeast1
  • サービス名: awesomeapi
  • 「未認証の呼び出しを許可」

「次へ」をクリックしたあと、「コンテナイメージのURL」でawesomeapi-imageのlatestを選択。

すると自動でGoogle Cloud Buildをつくってビルド、デプロイをしてくれる。

しかしこの状態では失敗する。Cloud RunでDockerを使う場合は8080ポートで通信を待つ必要があるからだ。

そのためDockerfileを修正して、gitにpush。

FROM python:3.8.5-slim

WORKDIR work

ADD awesomeapi /work/awesomeapi
ADD requirements.txt /work/requirements.txt
RUN pip install --upgrade pip && \
  pip install --no-cache-dir -r requirements.txt

CMD ["uvicorn","awesomeapi.main:app","--host","0.0.0.0","--port","8080"]

ビルドが自動開始するのを確認。完了したらCloud Runに戻って、Cloud RunのURLに飛ぶと、同様のレスポンスを取得できる。これでサービスが公開された。

5. FirestoreをつかってNoSQLを利用できるようにする

Google Cloud Firestoreに移動。「ネイティブモードを選択」をクリック。リージョンはasia-northeast1にして「データベースを作成」をクリック。

先にコレクションを作ってしまう。テストで次のようにした。

  • コレクションID: users
  • ドキュメントID: 自動割り当て
  • フィールド:

    • name: koboriakira (フィールドタイプ string)
    • age: 31(フィールドタイプ number)

つぎにFirestoreのデータを読み取れるように実装を進める。

fastapi
uvicorn
google-cloud-firestore
from typing import Optional
from fastapi import FastAPI
from google.cloud import firestore

app = FastAPI()
db = firestore.Client()

@app.get("/")
def read_root():
    docs = db.collection(u'users').stream()
    result = {}
    for doc in docs:
        result[doc.id] = doc.to_dict()
    return result


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
    return {"item_id": item_id, "q": q}

いったんローカルのテストは無視して、git commitそしてgit pushする。Cloud Buildが自動的にスタートする。

ビルドが成功してCloud Runのコンテナが最新化したことを確認したら、サービスのURLに飛ぶ。

レスポンスが{"4Ja4sXQ6DiIDX3N0kYmW":{"age":31,"name":"koboriakira"}}のようになっていれば、データベースを利用できていることがわかる。

6. Firestoreをつかった開発環境をつくる

さきにdocker-compose.ymlのportsも8080に修正しておく。

version: '3'
services:
  app:
    build: .
    ports:
      - '7777:8080'
    volumes:
      - ./awesomeapi:/work/awesomeapi

つぎにこのFirestoreを扱えるように認証まわりの設定をする。

「IAMと管理」 > 「サービスアカウント」に移動。<プロジェクトID>@appspot.gserviceaccount.comというアカウントがあるはずなので、クリックしてアカウント詳細画面へ。

画面下の「キー」で「鍵を追加」をクリック。JSONでキーを作成すると、jsonファイルがローカルに自動でダウンロードされる。

このファイルを内に配置し、誤ってpushしないように.gitignoreを編集する

.
├── Dockerfile
├── README.md
├── awesomeapi
│   ├── __pycache__
│   │   └── main.cpython-38.pyc
│   └── main.py
├── docker-compose.yml
├── my-awesome-api-290501-9b5ca70ce0e8.json
└── requirements.txt
# credentials
my-awesome-api-290501-9b5ca70ce0e8.json

(以下省略)

念のためgit statusでjsonファイルがgit管理対象でないことを確認しておく。

このjsonファイルをDockerコンテナに入れたいので、docker-compose.ymlを編集する。

version: '3'
services:
  app:
    build: .
    ports:
      - '7777:8080'
    volumes:
      - ./awesomeapi:/work/awesomeapi
      - ./my-awesome-api-290501-9b5ca70ce0e8.json:/work/credentials.json
    environment:
      - GOOGLE_APPLICATION_CREDENTIALS=/work/credentials.json

※Dockerfileにjsonファイルを含めるような編集をすると、のちの自動ビルド時にjsonファイルが見つからないためエラーになる。

docker-compose upでコンテナを開始して、localhost:7777にアクセスして、同様にデータベースを使っていることを確認する。

7. ビルド構成をファイル管理する

Google Cloud Buildの「トリガー」に移動して、作成されたトリガーの詳細に移る。

cloudbuild.yamlを作成し、詳細内にある「インライン」で定義されている設定ファイルの記述をコピペする。

.
├── Dockerfile
├── README.md
├── awesomeapi
│   └── main.py
├── cloudbuild.yaml
├── docker-compose.yml
├── my-awesome-api-290501-9b5ca70ce0e8.json
└── requirements.txt
steps:
  - name: gcr.io/cloud-builders/docker
    args:
      - build
      - '--no-cache'
      - '-t'
      - '$_GCR_HOSTNAME/$PROJECT_ID/$REPO_NAME/$_SERVICE_NAME:$COMMIT_SHA'
      - .
      - '-f'
      - Dockerfile
    id: Build
  - name: gcr.io/cloud-builders/docker
    args:
      - push
      - '$_GCR_HOSTNAME/$PROJECT_ID/$REPO_NAME/$_SERVICE_NAME:$COMMIT_SHA'
    id: Push
  - name: gcr.io/google.com/cloudsdktool/cloud-sdk
    args:
      - run
      - services
      - update
      - $_SERVICE_NAME
      - '--platform=managed'
      - '--image=$_GCR_HOSTNAME/$PROJECT_ID/$REPO_NAME/$_SERVICE_NAME:$COMMIT_SHA'
      - >-
        --labels=managed-by=gcp-cloud-build-deploy-cloud-run,commit-sha=$COMMIT_SHA,gcb-build-id=$BUILD_ID,gcb-trigger-id=$_TRIGGER_ID,$_LABELS
      - '--region=$_DEPLOY_REGION'
      - '--quiet'
    id: Deploy
    entrypoint: gcloud
images:
  - '$_GCR_HOSTNAME/$PROJECT_ID/$REPO_NAME/$_SERVICE_NAME:$COMMIT_SHA'
options:
  substitutionOption: ALLOW_LOOSE
substitutions:
  _TRIGGER_ID: 33942bf8-952d-4bf8-8bf0-52dd2d45409d
  _SERVICE_NAME: awesomeapi
  _DEPLOY_REGION: asia-northeast1
  _GCR_HOSTNAME: asia.gcr.io
  _PLATFORM: managed
  _LABELS: gcb-trigger-id=33942bf8-952d-4bf8-8bf0-52dd2d45409d
tags:
  - gcp-cloud-build-deploy-cloud-run
  - gcp-cloud-build-deploy-cloud-run-managed
  - awesomeapi

Google Cloud Buildのビルド構成を「インライン」から「Cloud Build構成ファイル」に変えて「保存」をクリック。

8. テストを作成して、CIを整える

テスト用に適当なメソッドをつくる。

.
├── Dockerfile
├── README.md
├── awesomeapi
│   ├── calculate.py
│   └── main.py
├── cloudbuild.yaml
├── docker-compose.yml
├── my-awesome-api-290501-9b5ca70ce0e8.json
├── requirements.txt
└── tests
    └── test_calculate.py
def add(a: int, b: int) -> int:
    return a + b
from awesomeapi import calculate


def test_add():
    assert calculate.add(2, 3), 5

ローカルでpytestを実行。

$ python -m pytest
========================== test session starts ===========================
platform darwin -- Python 3.8.2, pytest-6.0.1, py-1.9.0, pluggy-0.13.1
rootdir: /Users/canary/git/awesomeapi
collected 1 item                                                         

tests/test_calculate.py .                                          [100%]

=========================== 1 passed in 0.02s ============================

Docker上でもテストが行えるように、pytestをライブラリに追加し、testsフォルダをDockerイメージに追加する。

fastapi
uvicorn
google-cloud-firestore
pytest
FROM python:3.8.5-slim

WORKDIR work

ADD awesomeapi /work/awesomeapi
ADD tests /work/tests
ADD requirements.txt /work/requirements.txt
RUN pip install --upgrade pip && \
  pip install --no-cache-dir -r requirements.txt

CMD ["uvicorn","awesomeapi.main:app","--host","0.0.0.0","--port","8080"]

cloudbuild.yamlを編集して、テストを追加する。

steps:
  - name: gcr.io/cloud-builders/docker
    args:
      - build
      - '--no-cache'
      - '-t'
      - '$_GCR_HOSTNAME/$PROJECT_ID/$REPO_NAME/$_SERVICE_NAME:$COMMIT_SHA'
      - .
      - '-f'
      - Dockerfile
    id: Build
  - name: gcr.io/cloud-builders/docker
    args:
      - run
      - '$_GCR_HOSTNAME/$PROJECT_ID/$REPO_NAME/$_SERVICE_NAME:$COMMIT_SHA'
      - python
      - -m
      - pytest
    id: Test
(以下略)

gitにコミットして、Cloud Buildを走らせる。ビルドが通ればテストの自動実行設定もできた。

9. このあとやりたいこと

  • ビルド結果をSlack等に通知

    • そのアプリをつくる?
    • またはPubSubを勉強する必要がありそう
  • ストレージに保存したい

    • 保存はできた。バゲットを示す環境変数はCloud Runにも必要
    • svgを作成して保存したりしたい
    • svgは別途勉強しないといけない

パッカーズ対セインツのプレビューを書いて(あまり深い内容にならなくて反省)、とりあえず今日は終わり。明日以降はフロントエンドをやりなおして、APIサーバを利用したサイトを作ってみて終わり。

2020年9月25日、金曜日

今日も雨でBと散歩には行けなさそう。

ドルフィンズ対ジャガーズの試合をチラ見しながらReactとCircleCIの導入にチャレンジ。またkoboriakira.comをGoogle Domainsに移行した。サブドメインを使ったアプリ公開が楽になりそうなので嬉しい。


個人開発に関して、すこしまとめておこうと思う。

  • 管理

    • masterブランチとfeatureブランチで運用する
    • masterブランチにコミットしないようpre-commitを利用する
    • masterブランチにはPRでマージする
    • CircleCIを利用して、各コミットごと(PRごと)にテストを回す
  • フロントエンド

    • React + TypeScriptで開発
    • masterブランチをNetlifyにデプロイして公開する
  • バックエンド

    • Google Cloud Runを利用して、REST APIでサービス公開する
    • Cloud Runには、Google Cloud Build(Google Container Repository)を利用して自動デプロイを整える
    • データベースはGoogle Cloud Firestore、ファイル管理はGoogle Cloud Storageを利用する
  • 今後のチャレンジ

    • フロントの認証機能をつける
    • ネットワークやロギングについて改善できることがないか調べる
    • CIにおいて、テスト結果をSlackなどに連携させる
    • 上記のプロジェクトのテンプレートを用意する
    • Firestore、NoSQLを使ったモデリングのベストプラクティスを学ぶ
    • 他のGPCのサービスを利用する
    • gRPCを学ぶ

この数週間で個人でアプリケーション開発ができる状態にはなってきた。一方で運用フェーズでは必ず問題点が発生しそうな気がしている。なのでさまざまなことを出来るようにしつつ、公開後に必要となる知識もすこしずつ触れてみようかなと考えている。

とりあえずFirebase Authenticationを使って会員登録/ログイン機能を実装してみて(以前にやったけどもう忘れていた)、いよいよアプリケーション開発にとりかかる。シンプルなカウンターのアプリにするつもり。

来週のアウトプット

  • Google認証を実装する
  • カウントアプリのページ遷移をつくる
  • 保育園の見学プランを立てる(残り分)
  • 初日と2日目のパートをつくる
  • 試合に関する情報を集める

バックエンドはなんとか最初のステージを突破したので、フロントエンドを再学習しながらアウトプットに努める。


Kobori Akira

IT業界の社会人。音楽、パッカーズ、スワローズ、ポーカー。
読む価値のある記事はQiitaやnoteに投稿する予定です。
過去人気だったブログ記事はこちらから。