S3ファイル作成をトリガーにしてLambda起動
S3ファイル作成をトリガーにしてLambda起動させてみて、どんなオブジェクトがLambdaに渡されるのかを確認しました。
Lambda作成
Runtimeは今回は Node.js 14.x を選択しました。
ソースコードはこれだけ。
exports.handler = async (event) => { console.log(JSON.stringify(event)); };
S3イベント設定
マネジメントコンソール上で、S3のPropertiesのEvent notifications(プロパティのイベント通知)のところから設定します。
こんな感じ。
通知したいイベントのタイプはオブジェクト作成を選択しました。
下のほうにイベントの通知先の設定欄がありますので、先ほど作ったLambdaを選択します。
試してみる
$ echo Hello | aws s3 cp - s3://xxxxxxxx/eventsample/1.txt
CloudWatch Logsには以下のJSONが出力されました。
{ "Records": [ { "eventVersion": "2.1", "eventSource": "aws:s3", "awsRegion": "ap-northeast-1", "eventTime": "2021-04-20T15:45:05.043Z", "eventName": "ObjectCreated:Put", "userIdentity": { "principalId": "AWS:AIDAXXXXXXXXXXXXXXXXX" }, "requestParameters": { "sourceIPAddress": "35.xxx.xxx.xxx" }, "responseElements": { "x-amz-request-id": "3VVRXXXXXXXXXXXX", "x-amz-id-2": "xxxxxxxxxxxxxxxx" }, "s3": { "s3SchemaVersion": "1.0", "configurationId": "s3event-sample", "bucket": { "name": "xxxxxxxx", "ownerIdentity": { "principalId": "XXXXXXXXXXXXXX" }, "arn": "arn:aws:s3:::xxxxxxxx" }, "object": { "key": "eventsample/1.txt", "size": 6, "eTag": "09f7e02f1290be211da707a266f153b3", "sequencer": "00607EF70623E54228" } } } ] }
Google Compute Engineのstartup scriptsをgcloudコマンドで取得
Google Compute Engineのstartup scriptsを取得するコマンド。(INSTANCE_NAMEのところはインスタンスの名前を入れます)
$ gcloud compute instances describe INSTANCE_NAME --format json | jq '.metadata.items|from_entries."startup-script"' -r
説明
gcloud compute instances describe
コマンドで、startup scriptsをJSONで取得できるのですが、JSONでエンコーディングされてしまっています。長いスクリプトをこれで見るのは辛いです。
$ gcloud compute instances describe INSTANCE_NAME --format json | jq '.metadata.items' [ { "key": "startup-script", "value": "echo Hello\n" } ]
jq
コマンドで from_entries
というのを使うと、
[ { "key": "startup-script", "value": "echo Hello\n" } ]
を
{ "startup-script": "echo Hello\n" }
に変換できます。なので from_entries."startup-script"
と書くとスクリプトの部分を取得でき、 -r
を付ければできあがりです。
to_entries, from_entries, with_entries | jq Manual (development version)
おまけ:startup scriptsをインスタンスからインスタンスにコピー
gcloud compute instances add-metadata
コマンドの--metadata-from-file
は標準入力も受け取れるようですので、パイプでつないで以下のようにすれば、startup scriptsをインスタンスからインスタンスにコピーできました。
$ gcloud compute instances describe SRC_INSTANCE_NAME --format json | jq '.metadata.items|from_entries."startup-script"' -r | gcloud compute instances add-metadata DST_INSTANCE_NAME --metadata-from-file startup-script=/dev/stdin
2021/05/20 追記
インスタンスからはcurlコマンドでも自身のstartup scriptsを取得できます。
$ curl -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/instance/attributes/startup-script
Google Container RegistryにVue.jsアプリを置いてCompute Engineにデプロイ
Vue.jsのウェブアプリをDockerコンテナにし、コンテナイメージからGoogle Compute Engineのインスタンスを作成して、Vue.jsのウェブアプリを動かしてみます。
Vue.jsのプロジェクト作成
$ vue create sample
Vue 2 or 3 は2を選択しました。
$ cd sample $ npm run serve
サーバが立ち上がりますので、ブラウザでlocalhostにアクセスしてみます。ポート番号は npm run serve
を実行しているコンソール上に表示されているはずですが、たぶん8080番です。すでに使われていたら8081、8082と順番に空きを探してくれるみたいです。
コンテナイメージ作成
Dockerfile
は以下の内容です。
FROM node:15.14.0-alpine3.10 RUN npm install -g http-server WORKDIR /opt/app COPY package*.json ./ RUN npm install COPY . . # Dockerレイヤーのキャッシュを活用するために2段階のコピー RUN npm run build # distディレクトリに静的なファイルが生成される EXPOSE 80 CMD ["http-server", "dist", "-p", "80"]
以下のVue.js公式ドキュメントを参考にしました。
ビルドします。
$ docker build -t vuejs-sample .
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE vuejs-sample latest 9d7cbcd7bd1f About an hour ago 360MB
Google Container Registryにpush
Google Container Registryにpushします。
やり方はきのうの記事にも書きました。
Google Container RegistryにHello, WorldなDockerイメージを置いてみる
$ docker tag vuejs-sample gcr.io/xxxxxxxx/vuejs-sample $ gcloud docker -- push gcr.io/xxxxxxxx/vuejs-sample
Google Compute Engineを作成し、デプロイ
sample1
という名前のGoogle Compute Engineのインスタンスを作成します。その際にコンテナイメージを指定します。
$ gcloud compute instances create-with-container sample1 --container-image gcr.io/xxxxxxxx/vuejs-sample --tags http-server
このコマンドで作成されたインスタンスは、ディスク容量は10GB、マシンタイプn1-standard-1、OSはコンテナ実行に最適化された専用OS(Container-Optimized OS from Google documentation | Google Cloud)でした。
コンテナがLISTENしているポートは自動でホスト側のポートにもマッピングされます。
コンテナを実行する際のオプションの構成 | Compute Engine ドキュメント | Google Cloud
ただし、firewallの設定は必要なようです。
$ gcloud compute firewall-rules create allow-http --allow tcp:80 --target-tags http-server
作成したインスタンスのグローバルIPにブラウザでアクセスすると、さきほどのVue.jsのデモアプリが動いていることが確認できます。
Google Container RegistryにHello, WorldなDockerイメージを置いてみる
Google Container Registryを初めて触ってみます。
Dockerコンテナイメージ作成
以下のような適当なDockerfile
を作成します。
FROM ubuntu:20.04 RUN echo "Hello, World!" > /hello.txt
コンテナイメージをビルドします。Dockerfile
のあるディレクトリで以下のコマンドでビルドできます。
$ docker build -t hello-world .
作成したコンテナイメージは、docker images
コマンドで見えるようになります。
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE hello-world latest 21deb0595efa 4 minutes ago 72.9MB
試しにコンテナイメージを実行してみます。
$ docker run -it --rm hello-world bash root@3b8150776095:/# cat /hello.txt Hello, World!
Google Container Registryにpush
Google Container Registryにpushするために、タグを付けます。新しいタグの名前は gcr.io/PROJECT_ID/NAME というフォーマットです。PROJECT_IDはGoogle Cloud PlatformのプロジェクトID、NAMEはコンテナイメージの名前です。
$ docker tag hello-world gcr.io/xxxxxxxx/hello-world
docker images
コマンドで確認できます。
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE gcr.io/xxxxxxxx/hello-world latest 21deb0595efa 6 minutes ago 72.9MB hello-world latest 21deb0595efa 6 minutes ago 72.9MB
このコンテナイメージをGoogle Container Registryにアップロードします。
$ gcloud docker -- push gcr.io/xxxxxxxx/hello-world
$ gcloud container images list NAME gcr.io/xxxxxxxx/hello-world
Google Container Registryからpull
gcloudコマンドを同じプロジェクトが使えるように設定してあれば、別の環境からもコンテナイメージにアクセスできることが確認できます。
$ gcloud docker -- run -it --rm gcr.io/xxxxxxxx/hello-world bash -c "cat /hello.txt" Hello, World!
追記 2021/04/22
AWSでも同じことをしました。
Google Compute Engineのstartup scriptsを試してみる
Google Compute Engineのstartup scripts(起動スクリプト)の機能を試してみました。
(最近GCPの「やってみた」系の記事が続いています)
- Google Cloud TranslationのAPIをローカルのNode.jsから試してみる
- FirebaseのCloud FunctionsからGoogle Cloud Translationを試してみる
- FirebaseのAuthenticationをVue.jsから試してみる
- Google Cloud BuildをGitHubリポジトリで動かしてHello, World!
やってみる
インスタンス作成。
$ gcloud compute instances create sample1 --image-project ubuntu-os-cloud --image-family ubuntu-2004-lts
startup-script
という名前のメタデータとしてスクリプトの中身を文字列で直接指定すればよいようです。起動した日時をテキストファイルに書き残すスクリプトを指定してみました。
$ gcloud compute instances add-metadata sample1 --metadata startup-script="date > /startup-date.txt"
メタデータを確認してみます。
$ gcloud compute instances describe sample1 --format json | jq ".metadata.items" [ { "key": "startup-script", "value": "date > /startup-date.txt" } ]
起動していたインスタンスだったので、いったんSTOP。
$ gcloud compute instances stop sample1 Stopping instance(s) sample1...done. Updated [https://compute.googleapis.com/compute/v1/projects/xxxxxxxx/zones/asia-northeast1-a/instances/sample1].
STOPが完了するまでコマンドは待ってくれるようです。
続いて起動。
$ gcloud compute instances start sample1 Starting instance(s) sample1...done. Updated [https://compute.googleapis.com/compute/v1/projects/xxxxxxxx/zones/asia-northeast1-a/instances/sample1]. Instance internal IP is 10.xxx.xxx.xxx Instance external IP is 34.xxx.xxx.xxx
startコマンドも起動が完了するまで待ってくれます。しかも、起動完了したら、取得したIPアドレスがコンソールに表示されるんですね。便利。
$ ssh suzuki@34.xxx.xxx.xxx cat /startup-date.txt Mon Apr 12 13:49:34 UTC 2021
add-metadata
コマンドで直接スクリプトの中身を指定するのではなく、ローカルファイルに置いてから指定することもできるようです。
ローカルのカレントディレクトリに startup-script.txt
という名前でスクリプトを置きます。
$ cat > startup-script.txt echo Hello >> /startup-date.txt date >> /startup-date.txt
このファイルをメタデータで指定します。ファイルの中身がメタデータで登録されます。
$ gcloud compute instances add-metadata sample1 --metadata-from-file startup-script=./startup-script.txt
$ gcloud compute instances describe sample1 --format json | jq ".metadata.items" [ { "key": "startup-script", "value": "echo Hello >> /startup-date.txt\ndate >> /startup-date.txt\n" } ]
再起動。
$ gcloud compute instances stop sample1 && gcloud compute instances start sample1 Stopping instance(s) sample1...done. Updated [https://compute.googleapis.com/compute/v1/projects/xxxxxxxx/zones/asia-northeast1-a/instances/sample1]. Starting instance(s) sample1...done. Updated [https://compute.googleapis.com/compute/v1/projects/xxxxxxxx/zones/asia-northeast1-a/instances/sample1]. Instance internal IP is 10.xxx.xxx.xxx Instance external IP is 34.xxx.xxx.xxx
$ ssh suzuki@34.xxx.xxx.xxx cat /startup-date.txt Mon Apr 12 13:49:34 UTC 2021 Hello Mon Apr 12 13:58:34 UTC 2021
追記 2021/04/20
RubyでマルチスレッドなTCP Serverのサンプルコード
RubyでTCP/IPのソケットを試してみました。サーバ側はマルチスレッドです。
サーバ側サンプルコード
server.rb
require 'socket' maxlen = 10 server = TCPServer.open(3000) loop do Thread.start(server.accept) do |socket| begin loop do buf = socket.readpartial(maxlen) # クライアントから受信 socket.write(buf) # そのままクライアントに返答 $stdout.write(buf) # 動作確認のためサーバ側標準出力 end rescue EOFError => e $stdout.write("eof\n") # 切断 rescue => e print e.backtrace.join("\n") ensure socket.close end end end
クライアント側サンプルコード
client.rb
require 'socket' maxlen = 10 TCPSocket.open("localhost", 3000) do |socket| t1 = Thread.start do socket.write("Hello\n") # サーバに送信 sleep(1) socket.shutdown() # 1秒たったらクライアントから切断 end t2 = Thread.start do begin loop do buf = socket.readpartial(maxlen) # サーバから受信 $stdout.write(buf) # 動作確認のためクライアント側標準出力 end rescue EOFError => e $stdout.write("eof\n") # 切断 end end t1.join # スレッドが終了するまで待つ t2.join # スレッドが終了するまで待つ end
実行例
サーバ側。起動しただけだと黙って待ち受けてます。
$ ruby server.rb
クライアント側。Helloと送信し、同じテキストを受信し、1秒後に切断します。
$ ruby client.rb Hello eof
クライアントを複数の端末を使うなどして同時に起動してもサーバ側は並行して処理できています。
$ ruby server.rb Hello eof Hello eof Hello Hello eof eof
(Javaならレファレンスを見ながら書けば動くだろうという安心感(自信)があります。が、他の言語だとこういうのは、実際に書いて試行錯誤しないと私は書けないです)
AWS SQSをRubyで試してみる
RubyからAWSのSQSにメッセージを送受信してみます。前回はPythonで試しましたが、今回はそのRuby版です。
CloudFormationでSQSを作成するところは前回と同じなので省略します。
$ gem install -N aws-sdk
Rubyのサンプルコード samplr.rb
。
require 'json' require 'aws-sdk' profile = "default" credentials = Aws::SharedCredentials.new(profile_name: profile) sqs_client = Aws::SQS::Client.new(credentials: credentials) queue_url = "https://ap-northeast-1.queue.amazonaws.com/xxxxxxxxxxxx/samplesqs-SampleSQS-XXXXXXXXXXXX" # 送信 idx = 1 while idx <= 10 do # 10個のメッセージを送信 message = {msg: "Hello, world!", foo: idx} sqs_client.send_message({ queue_url: queue_url, message_body: JSON.dump(message), }) idx += 1 end # 受信 loop do res = sqs_client.receive_message({ queue_url: queue_url, }) # 受信したものをなにか処理 (このサンプルでは表示するだけ) delete_entries = [] # 処理済みメッセージ一覧 id = 1 for msg in res.messages do message = JSON.parse(msg.body) p message delete_entries.push({id: id.to_s, receipt_handle: msg.receipt_handle}) id += 1 end # 処理済みメッセージを削除 if delete_entries.size > 0 then # 0件でdelete_message_batchを呼び出すとエラーになる sqs_client.delete_message_batch({ queue_url: queue_url, entries: delete_entries, }) else break end end
実行してみます。
$ ruby sample.rb {"msg"=>"Hello, world!", "foo"=>5} {"msg"=>"Hello, world!", "foo"=>1} {"msg"=>"Hello, world!", "foo"=>9} {"msg"=>"Hello, world!", "foo"=>2} {"msg"=>"Hello, world!", "foo"=>7} {"msg"=>"Hello, world!", "foo"=>10} {"msg"=>"Hello, world!", "foo"=>3} {"msg"=>"Hello, world!", "foo"=>4} {"msg"=>"Hello, world!", "foo"=>6} {"msg"=>"Hello, world!", "foo"=>8}
できました。順序は保証されていないので、受信したメッセージの順番はめちゃめちゃです。