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

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

2017/8/16 GCP HTTP Load Balancerがしばらく502を返し続けた問題

朝起きたらoceanusの死活監視のメールがきてた。日本時間で8/16 1:18から4:29。

いつものBigQueryが落ちてたのかと思ってログを見たけど、アプリケーション側では目立ったエラーが見当たらなかった。

エラー時のレスポンスは502と下記内容。

<html><head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>502 Server Error</title>
</head>
<body text=#000000 bgcolor=#ffffff>
<h1>Error: Server Error</h1>
<h2>The server encountered a temporary error and could not complete your request.<p>Please try again in 30 seconds.</h2>
<h2></h2>
</body></html>

アプリ側では502を返すことなんてない(ネットワーク内でRedis等につながらない時は503を返す)。

だから、ロードバランサーだ。

HTTPロードバランサーのログをみると下記のような感じで502が並んでいた。

アプリや自分で用意したヘルスチェックのあるGKEまで到達していない。


f:id:uyamazak:20170816104523p:plain

GCPのインシデントにも見当たらなかった。

Google Cloud Status Dashboard



深夜帯でアクセス数も少ないので、大きな問題はないけど調査続行中。

GCP公式アイコンを使って資料を作る

自社データの収集活用プロジェクトのoceanusで、データスタジオを使い始めたり、複雑になってきたので全体図を書き直した。

アイコンや矢印は公式のGoogleスライド形式のアイコンがあったので、そちらからコピーしてGoogle図形描画で作成。普通にコピペで使えるので便利。

docs.google.com




そのほかの形式もこちらに用意されている。
cloud.google.com


↓これ
f:id:uyamazak:20170816160317p:plain

AWSかGCPか悩んだら時の判断基準は新規か既存か

私の中では下記が一番の判断基準かなと思った。


新規プロジェクトだったらGCP

既存プロジェクトだったらAWS


最近はGCPもPostgoreSQLに対応したりしてるけど、すべてのサービスがGoogle内で使う前提なので、種類は限られている。

AWSはサービス数を見ればわかるけど、ユーザーが使っているものをどんどんクラウド化して、AWSにすべて移せるようにしているイメージ。

そのため、現状例えばフルマネージドのRedisが欲しいとなったらAWSしかないし、既存の様々なものに対応できるとなるとAWSの方が柔軟性が高い。

当社でも10年前から運用しているbizocean本体であれば、そのままGCPに移すことはできず、どうしてもクラウド化するとなれば現状AWSしかない。

なぜ新規だったらGCPがいいのかといえば、新規であればGCPに合わせて設計できるし、コストはGCPの方が安いこと、それぞれの機能が強力に連携しておりシンプルに設計できることにあると思う。

あとG Suite(旧Google Apps)のユーザーであれば、Gmailなどで使っているアカウントがそのままGCPでも利用でき、権限管理が非常にシンプルになるメリットもある。

アカウント維持が有料なので、やめた人のアカウントと権限がそのまま残り続けることは、ほぼ確実に防ぐことができる。

会社の研修でシリコンバレー&サンフランシスコ行ってきた 日本ヤバい

株式会社ビズオーシャンで、2017年6月下旬に4泊6日で社長賞?的なシリコンバレー研修に4人+社長で行ってきたので、思ったことなどをメモ。

一言で言うと

日本ヤバい

日程など

4泊6日
日曜日午後に出発して金曜午後に帰ってきた。

飛行機は話題のUNITED。帰りの最後のごはんが機材の故障かなんかで出なかった以外は特に問題なかった。

泊まったホテル

ホテルは、航空券のみと大して値段の変わらないレベルの安いホテル。経費なので問答無用で判断基準は「サンフランシスコにあって価格安い」のみ。

ホテル ウィットコム予約 - サンフランシスコ | エクスペディア

歴史のある建物のようでかなり古いけど、手入れはしっかりしており、掃除などはしっかりされた。

1階にボールルームがあり、下記LGBTイベント関連かなんかやっていた。ボールルームの意味は最近アニメを見て知った。

TVアニメ「ボールルームへようこそ」公式サイト


サンフランシスコの中心部(ユニオンスクエア?)から徒歩20分程度と、かなりはずれで暗くなると道端にあやしい人たちがたむろしている女性にはおすすめできない地域だけど、男5人だから特に問題なかった。

サンフランシスコとシリコンバレーの関係

行ってみるまでサンフランシスコとシリコンバレーに強い関係があるのは知らなかった。

サンフランシスコのイメージは、GTAでの路面電車と、昔のミスタードーナツのCMでサンフランシスコのチャイナタウンの飲茶という所さんの歌が耳に焼き付いていたぐらいだった。

しかし、2つは大きく影響していた。

距離は混み具合によるが車で1時間弱。

詳しくは下記参考記事だけど、
サンフランシスコは上場前、シリコンバレーは上場後
サンフランシスコは独身若者、シリコンバレーは家族持ち
サンフランシスコは時間関係なくがんばる、シリコンバレーは定時帰り
サンフランシスコは街中、シリコンバレーは郊外の大学


blog.btrax.com


シリコンバレー、サンフランシスコ界隈IT企業地図|インターネット界隈の事を調べるお
この記事の画像が一番ひと目で分かりやすかった

http://file.takanoridayo.blog.shinobi.jp/dayo150617sv.png

中に入って話を聞けた場所

SAP

現地日本人社員である小松原さんに案内していただきました。
ドイツのERPをはじめとした超大企業。会長がスタンダード大学に自費でデザイン思考のd.schoolを援助し、さらにシリコンバレーで始めたHANAをはじめとした事業で既存事業を抜いたという成功事例に圧倒される。
コンクリートむき出しの床は、あえて中途半端な状態にしておくことで、何かしなきゃという創造性を増すためという理由をしった。日本だとただオシャレなだけかと思ってた。

シリコンバレーオフィスでは、デザイン思考的なアイデアのネタになるようなものでいっぱいだった。

f:id:uyamazak:20170725110158j:plain

f:id:uyamazak:20170725105510j:plain

f:id:uyamazak:20170725105450j:plain

f:id:uyamazak:20170725110145j:plain

スタンフォード大学 d.school

上記したデザイン思考用の建物。

f:id:uyamazak:20170725110321j:plain

デザインを考える道具自体もデザイン思考で作られており、机、いす、ホワイトボード、工具などすべてに深い理由があった。

日本で有名なスタンフォード大学の本はこの2冊だろうか。

スタンフォードの自分を変える教室 (だいわ文庫)

スタンフォードの自分を変える教室 (だいわ文庫)

20歳のときに知っておきたかったこと スタンフォード大学集中講義

20歳のときに知っておきたかったこと スタンフォード大学集中講義

  • 作者: ティナ・シーリグ,Tina Seelig,高遠裕子
  • 出版社/メーカー: CCCメディアハウス
  • 発売日: 2010/03/10
  • メディア: ハードカバー
  • 購入: 475人 クリック: 17,353回
  • この商品を含むブログ (395件) を見る

どちらも邦題には商業的に変えているのか、疑問が残るので、まだ読んでない人はぜひ英語の原題を知ってから考えてほしい。


社長のテンションはここが最高だった。

Uber

言わずと知れたスマホでタクシー的なものを呼べるもの。意図せず宿泊したホテルから2ブロックくらいの距離ですぐ行けた。

実際に現地での移動にUberを使ったが、事前にGoogleマップで道のりと金額が決まり、カード決済できるので、ぼったくりの心配もないし、行先を伝える苦労も間違いもない。

昨年あたりサンフランシスコではタクシー会社のイエローキャブが破産したのが日本でも話題になってたのを思い出す。


通訳をお願いした方の以前の部下がUberにいてオフィスの案内とお話を聞く機会をセッティングして頂いた。

人事制度とかシリコンバレーとサンフランシスコの違いとか大変いい話を聞かせて頂いた。



Googleマップをかなり使ってるサービスだが、それだけでなく、社員も元Googlerが多く、様々な制度もGoogleから持ってきているとのこと。

Googleの影響力はサンフランシスコのスタートアップにもとてつもなく大きい。

けどGoogleやめてスタートアップ行くのがたくさんいるって、どれだけのリターンを見込んで転職しているんだろう。



オフィスも広くて、様々な設備が充実していた。
エンジニアの机の3人に一人ぐらいは、ディスプレイの高さを上げて、スタンディングで仕事ができるのが設置されていたのがぱっと見で印象的だった。


フリーのドリンクと、シリアルコーナー
f:id:uyamazak:20170725105122j:plain
f:id:uyamazak:20170725105103j:plain


www.gizmodo.jp

中の人とアポがとれず観光的に回ったところ

Google

Androidの人形の写真撮ったり。ショップはそんなに良いものなかった
f:id:uyamazak:20170725104948j:plain

Facebook

一般人は入れるところはない。看板の写真を撮るぐらい。

Twitter

ホテルのすぐ近くだったので看板を見たぐらい。
同じビルか、隣のビルにジムやブルーボトルコーヒーがあった。

f:id:uyamazak:20170725104515j:plain

Computer History Museum

一時間ぐらいしかなくて、駆け足だったけど、真空管だらけ配線だらけのマシンとかにテンションがあがる
www.tripadvisor.jp

多様性の力

サンフランシスコに到着した日、偶然にも大きなイベントが開催されていて、大通りを占拠し、歩道も人だかりがすごかった。

LGBTをはじめ、民族、人種などいろんな人達がパレードしていた。

www.sfpride.org

f:id:uyamazak:20170725104609j:plain


このイベントに合わせてか、Facebookの看板も虹色になっていたり、いろんな会社、お店で虹色を出していた。

多様性を受け入れさせようとする圧力、それを受け入れる寛容さ、どちらも強いエネルギーを持っていると感じた。

SAPでも根底に必要なのが多様性だと言っていた。年齢、国籍、文化、価値観の違う様々な人が一緒にいて、同じ目標を持つことで、課題も、解決策もいいものが出てくるのだろう。ここは日本が非常に劣っている部分だと感じる。

シリコンバレーの物価・給料の高さと日本のヤバい

SVではエンジニアの給料が急騰しており、人材確保が大変になっている。日本も同じだがレベルが違う。

普通のエンジニアで年収30万ドル、その他、コンピューターサイエンス系の学部卒業者などは数万ドル単位で、各種手付金とか手当とかを与えて来てもらうようにしているらしい。

優秀なエンジニアや、役職付きはいったいいくらなんだろう。

人月で考えるのは好きじゃないけど、外部のエンジニアを1ヶ月確保するには、400万円程度かかることになる。

日本を高めに見積もっても1/4程度になる。

当然この額を出せるということは、リターンを見込めるということだ。

こういうサービス、製品を作ったらexponential(指数関数的に、SAPで流行っている言葉らしい)に成長できるぞとみんな思えているんだと思う。

日本のエンジニアは英語は苦手かもしれないけど、技術的に大きく、少なくとも金額の差と同じ程度劣っているとは思えないし、この金額差はとても悔しく感じた。cheap japanがここにもあった。

それなのにこの金額差で出てしまうのは、為替の歪みも多少はあるかもしれないけど、日本向け(~1億人)にやっているかグローバル規模前提(数十億)でやっているかの差にも思えた。そもそも作ろうとしているもののレベルが違う。

純化すると、大成功して10万ユーザーになりそうなサービスと10億ユーザーになりそうなサービスなら、後者に1万倍投資しても適正どころか、それぞれに必要な開発・運用コストの差は1万倍以下にならないと絶対にどこかおかしいので、適正な投資金額は桁外れな差になるだろう。


いちエンジニアの視点でこれを解決するには、エンジニアが英語などのコミュニケーション能力や、家庭などの問題を克服して海外に出ていくか、日本にいてもグローバルを前提にサービス、製品をつくることが必要になると思った。


日本人エンジニアは優秀、という前提が正しければ、そのコストパフォーマンスにおいて日本はとてつもない力を発揮するかもしれない。


思えば、自動車、家電などの日本製品が世界を席巻したらしい華やかな昔も、「高くて良い」ではなく、「安くて良い」だったんじゃないかという気がする。1ドル360円固定もあったし。その時を知らないけど。

日本車で高級車といえるのはレクサスからぐらいではないだろうか。それでもポルシェとかマイバッハロールスロイス等いわゆる高級車に比べれば安い気がする。

私には値段の意味が分からないレベルの高級ブランドで海外に通用する日本製のものが思いつかない。

海外で見かけるのはコンビニ、牛丼、ラーメン、ユニクロローカライズされた寿司ぐらい。

日本の強みが今もそこにあるのなら、iPhoneUberGoogle検索みたいなイノベーションは諦めて、ソフトウェアやインターネットを使ったサービスも「安くて良い」、いわゆるトヨタ的「カイゼン」で突き進むという道もあるかもしれない。

国籍、人種とトランプ政権

圧倒的にインド人と中国が多いそう。ソフトウェアはアジア人の方が有利なんだろうか。どちらも国の家族のためにたくさん稼ぐという意識が強いのかもしれない。

しかし、トランプ政権の影響で、移民を厳しくしており、それも給料の高騰を招いているらしい。

じゃあ、アメリカ以外、せめてシリコンバレー、サンフランシスコ以外でやればいいじゃないかと思うけど、そういう人があの土地にリアルに集まってないとできない何かがあるんだと思う。


たまたま自分のブックマークを見直していて、チームラボの人が下記記事で同じようなことを言っていたのを見つける。この人も多様性の重要性を述べている。
組織の強みは唯一、知を発見できるかで決まる ——チームラボ代表・猪子寿之 | 猪子寿之氏が語る、これからのクリエイション|DIAMOND ハーバード・ビジネス・レビュー

たくさんの人が集まって、同じ時間を共有して仕事をするのは、非効率のようにも思えますが。

 やるしかありません。なぜなら、それがクリエイションだから。効率を超越してそうせざるを得ないんです。みんなが集まらなくてもいい、もっと効率的な方法があるのかもしれないけど、いまのところ、それは見つけられていないと思います。

英語

あっちにいる大多数が英語ネイティブじゃない中国人、インド人だと知ると、英語を使う際の恐怖みたいなものはちょっとなくなった気がする。

GihHubとかStackoverflowでガンガン攻めるべき。日本国内向けサービスは触らない方がいいかもしれない。最近はスマホも言語を英語にしてOK Googleしてる。はてなブログはどうだろう。

Google Cloud Storageにプロジェクトのディレクトリをまるごとバックアップする

gitはバックアップではないとどこかで聞いたのが印象的で、いつかやろうと思ってたのをやった。

たしかにパスワードとか環境変数もろもろはgitに入れていないので、開発サーバーが死んだときは大変面倒なことになってしまう。

今のところ、GCPで済ましているのでバックアップ先もCloud Storageに。

とりあえず手で思いつく通りやったのをファイルにまとめて実行してみたらできた。

#!/bin/bash

BACKUP_FILENAME="oceanus-backup-`date +'%Y-%m-%d-%I-%M-%S'`.tar.gz"
echo $BACKUP_FILENAME

gcloud config configurations activate oceanus
tar --exclude-vcs -zcvf $BACKUP_FILENAME /path/to/project
gsutil cp $BACKUP_FILENAME gs://path-to-gs-bucket
rm $BACKUP_FILENAME

こけた時の通知などが今後必要になる。

.gitのフォルダはいらないなと思って、検索してたらtarの--exclude-vcsというオプションを見つけた。.gitをはじめバージョン管理系のファイルを省くルールが適応されて自分で書かなくてよかったので便利。

あと実行するマシンでは複数のGCPプロジェクトを使っているので、gcloud config configurations activateで対象のプロジェクト設定に切り替えている。

設定の追加は下記が参考になる

gcloud config configurations create  |  Cloud SDK  |  Google Cloud Platform


料金は、とりあえず無難にRegional Storageにしたので、1GB $0.023/month。

1年後の毎月の料金を計算してみると、1回のバックアップで7MB弱だったので、今後増えて10MBになるとして計算してみると

0.01GB * 365days * $0.023 = $0.08395/month

1年回して、毎月約9円。

10倍になっても90円なら問題ない。さらにデータを滅多に触らないことが分かったらNearlineなりColdlineなりにすればさらに節約できそう。

毎日cronで実行するとして、触らない日もあるので、前日との差分がなかったらアップしないとかも考えたけど、この値段なら全部保存しちゃっていいかなと思った。


Google Cloud Storage の料金体系  |  Cloud Storage ドキュメント  |  Google Cloud Platform

Google Clouc Load Balancing+Let's EncryptのSSL証明書更新を完全自動化

先日書いたGoogle App Engineに引き続き、GKEと一緒にメインで使っているGoogle Clouc Load BalancingのSSL証明書更新も完全自動化するスクリプトを書いた。

uyamazak.hatenablog.com



github.com

GAEの方ではやらなかったけど、LBの方は一つの証明書で複数ドメイン対応できるように書いた。

このスクリプトをgcloudコマンドの認証を通したユーザーのcrontabで毎日実行すれば、期限30日前には勝手に更新されるはず。

もちろん止まった場合に期づけるように期限の監視は必要。

これでGCPで公開するサービスがいくら増えてもSSL証明書の更新忘れと手間の問題は解決したかもしれない。しかもランニングコストはほぼ無料。

GAEのSSL証明書更新をLet’s Encryptで完全自動化する

追記 2017/7/4 githubにうp
github.com


先週は会社の社長賞研修でサンフランシスコ、シリコンバレーに行ってきて書きたいことは山ほどあるけど、忘れてしまいそうな作業メモ。

研修中に証明書の期限が切れてしまい、リモートで更新するのが非常にストレスだったので、SSL証明書を自動化したい。


以前も考えたけど、GAEの証明書はまだgcloudコマンドからは変更できなかったので諦めた。

調べなおしてみると、2017/6/14のgcloudのアップデートで、コマンドラインからGAE用のSSL証明書をアップできるようになって、実現できるようになったので早速つくってみる。



とりあえずコマンド一発で
TOKENの作成
DNSの変更
AppEngineの証明書をアップデート
まで動いた。

あとcronで回して、念のため有効期限の監視も付ければ完璧?

監視は、デフォルトでは期限まで30日以上あったら更新されないので、期限が30日を切ったタイミングでアラート出せば何らかの原因で止まっているのが確認できるはず。


証明書にはLet's Encrypt
letsencrypt.org

やり取りは、おなじみの↓DNS認証ができるdehydratedを使う。
GitHub - lukas2511/dehydrated: letsencrypt/acme client implemented as a shell-script – just add water

前提として使うドメインはCloud DNSに入れておく必要がある。また、実行ユーザーはGoogle Cloud SDKをインストールして、最新のgcloudコマンドを使えるようにしておく。GAE使ってる環境と同じであれば大丈夫だとは思うけど。

元になるドメイン(bizocean.jp)は、AWS Route53で管理していたけど、今回使うサブドメイン(s.bizocean.jp)のNSレコードだけCloud DNSのものに向けておけば、問題なく動いた。

オリジナルのhook.shをベースに作る。とりあえず変更が必要そうなのは頭にまとめた。


gae_hook.sh

#!/usr/bin/env bash
DNS_PROJECT="oceanus-dev"
GAE_PROJECT="oceanus-gae"
CERT_ID=`gcloud beta app --project $GAE_PROJECT ssl-certificates list | awk 'NR==2 {print $1}'`
CERT_NAME="letsencrypt-auto`date "+%Y%m%d"`"
TARGET_DOMAIN="s.bizocean.jp"
ZONE_NAME="s-bizocean-jp"
ACME_TTL=60
SLEEP_SECOND=60

function deploy_challenge {
    local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"

    if [ $DOMAIN = $TARGET_DOMAIN ];then
      echo "Set TXT record of _acme-challenge.$DOMAIN to $TOKEN_VALUE"
      echo "dns update start"
      gcloud dns --project=$DNS_PROJECT record-sets transaction start -z=${ZONE_NAME}
      gcloud dns --project=$DNS_PROJECT record-sets transaction remove \
        `gcloud --project=$DNS_PROJECT dns record-sets list -z=${ZONE_NAME} --name="_acme-challenge.${DOMAIN}." | awk 'NR==2 {print $4}'` \
         -z=${ZONE_NAME} --name="_acme-challenge.${DOMAIN}." --type=TXT --ttl=${ACME_TTL}
      gcloud dns --project=$DNS_PROJECT record-sets transaction add $TOKEN_VALUE -z=${ZONE_NAME} --name="_acme-challenge.${DOMAIN}." --type=TXT --ttl=${ACME_TTL}
      gcloud dns --project=$DNS_PROJECT record-sets transaction execute -z=${ZONE_NAME}
      echo "dns update end sleep ${SLEEP_SECOND}"
      sleep $SLEEP_SECOND
    else
      echo "Don't match $TARGET_DOMAIN and $DOMAIN"
      echo "Set TXT record of _acme-challenge.$DOMAIN to $TOKEN_VALUE manually"
      read
    fi
}

function clean_challenge {
    local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
}

function deploy_cert {
    local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}"
    if [ $DOMAIN = $TARGET_DOMAIN ];then
      echo "update ssl cert start"
      gcloud beta app --project ${GAE_PROJECT} ssl-certificates update $CERT_ID \
        --display-name=$CERT_NAME \
        --certificate=$FULLCHAINFILE \
        --private-key=$KEYFILE
    else
      echo "Don't match $TARGET_DOMAIN and $DOMAIN"
    fi
}

function unchanged_cert {
    local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}"

}

HANDLER=$1; shift; $HANDLER $@

全体的にひたすらgcloudコマンドを並べただけで汚い。

更新後のsleepは10秒程度だと安定しなかったので、長めに60秒とした。TTLと合わせてもっと短くできるかもしれない。もしくはステータスを確認してからやるか。

ドメインチェック以外、例外処理もやってないが、それは死活監視の方に任せるつもり。


次に必要な設定。
鍵の長さはデフォが4096、GAEは2048以下しか対応していないのでアップ時に怒られる。
実行するディレクトリにconfigというそのままの名前のファイルを作成して変更する。

config

KEYSIZE="2048"

実行コマンド。-xを追加すれば残り期限にかかわらず強制定期にアップデートする。

./dehydrated -c -d s.bizocean.jp --challenge dns-01 -k ./gae_hook.sh

こんな感じで実行されればおk。
途中でこけるとトランザクションが残っちゃうので実行前にabortした方がいいかもしれない。

# INFO: Using main config file /home/yu_yamazaki/letsencrypt/dehydrated/config
Processing s.bizocean.jp
 + Checking domain name(s) of existing cert... unchanged.
 + Checking expire date of existing cert...
 + Valid till Oct  2 00:26:00 2017 GMT (Longer than 30 days). Ignoring because renew was forced!
 + Signing domains...
 + Generating private key...
 + Generating signing request...
 + Requesting challenge for s.bizocean.jp...
Set TXT record of _acme-challenge.s.bizocean.jp to RsMwB34gCgdfmks-2LGpsH0MVpBZVYkdveANiAcTXlk
dns update start
ERROR: (gcloud.dns.record-sets.transaction.start) transaction already exists at [transaction.yaml]
Record removal appended to transaction at [transaction.yaml].
Record addition appended to transaction at [transaction.yaml].
Executed transaction [transaction.yaml] for managed-zone [s-bizocean-jp].
Created [https://www.googleapis.com/dns/v1/projects/oceanus-dev/managedZones/s-bizocean-jp/changes/21].
ID  START_TIME                STATUS
21  2017-07-04T01:44:46.816Z  pending
dns update end sleep 60
  + Responding to challenge for s.bizocean.jp...
 + Challenge is valid!
 + Requesting certificate...
 + Checking certificate...
 + Done!
 + Creating fullchain.pem...
 + Done!

期限内だったら何もせずに終了する

% ./dehydrated -c -d s.bizocean.jp --challenge dns-01 -k ./gae_hook.sh
# INFO: Using main config file /home/BIZOCEAN/yu_yamazaki/letsencrypt/dehydrated/config
Processing s.bizocean.jp
 + Checking domain name(s) of existing cert... unchanged.
 + Checking expire date of existing cert...
 + Valid till Oct  2 00:46:00 2017 GMT (Longer than 30 days). Skipping renew!


今度はHTTPSロードバランサーの方も自動化したい

プログラミング Google App Engine

プログラミング Google App Engine