ポン酢まいるど開発記

開発のことから、ITにまつわることの感想まで、マイルドに書きます

AWSの各サービスを舐める (3) - Amazon S3

AWSのサービス舐めるシリーズその3。今回はS3です。
Kubernetesの検証の時にお世話になったので、今回はちょっとしたメモ程度で。 以前どんなことに使ったかは過去記事をご参照ください。

特徴と制約

  • レコードを格納するようなDBではなく、ファイル(CSV, JSON, Image, etc...)を格納するオブジェクト・ストレージ

  • ファイルの保存だけでなく、HTML/CSS/JSのファイルを置いてウェブサイトホスティングができます。

    • フロントエンドアプリ(React, Vue.js, Angularとか)ならバンドルして置くだけで良さげ。
    • PHPやNode.js, Javaのようなサーバーサイドスクリプトはおいてもアプリは動かない。

    • リージョンによって、ホスティングするときのサイトのエンドポイント名の命名規則が若干違うらしいです。

      • <bucket-name>.s3-website-<AWS-region>.amazonaws.com
      • <bucket-name>-s3-website-<AWS-region>.amazonaws.com

docs.aws.amazon.com

  • ほぼ無限にオブジェクトを格納できる

    • ただし、バケット数の上限は100個/アカウント.
    • 1オペレーション(PUT)で格納できるファイルサイズ上限は5GB.
    • 複数回に分けて格納した場合は格納できるファイルサイズ(1ファイル)の上限は5TB.

ストレージのタイプ

  • 高頻度(ミリ秒単位でパフォーマンスが求められる)のアクセス

    • STANDARD ... デフォルトの設定
    • RRS ... 非推奨
  • 低頻度のアクセス

    • STANDARD_IA ... AZにコピーして保存
    • ONEZONE_IA ...
  • ほとんどアクセスしないアーカイブ

    • Amazon Glacierを使う. S3のライフサイクル管理で一定期間経ったらS3 --> Glacierに移動させることができる.

イベント通知

  • バケットに対して特定のオペレーションをかけたときにAWSの他のサービスに通知送信・起動ができるみたい.
  • 通知先は、SNS, SQS, Lambdaの3種類
    • Lambdaの直接キックもできる。

ファイルの格納はもちろんですが、静的ファイルホスティングやイベント通知はかなり使い勝手が良さそうですね。 イベント通知のとこは別のアプリ(オンライン, 定刻起動のジョブとか)でS3にファイル置く -> Lambdaキックとかパイプライン組めそうなので、次回にやってみて記事に載せようと思います

AWSの各サービスを舐める (2) - DynamoDB

AWS舐めるシリーズその2。 今回はAmazon DynamoDBです。前回は文字ばかりだったので、チュートリアルを元にプログラム(Node.js)からも操作してみます。

docs.aws.amazon.com

ちなみに、ローカルマシンのTerminalから弄るので、アクセスキーとシークレットキーを使ってます。Elastic BeanstalkやECS, Lambadaから弄る時はIAMでロールを作ってから操作しましょう。

DynamoDB

テーブル作成

テーブルを作成してみます。 aws configureで設定していない時は認証失敗して以下のように弾かれるんですね。うまくいったときはdataに結果が全て入っていると。 正直プログラムからテーブル作るよりもCloudFormationでテーブル作成したほうが冪等性の担保とソース管理がしやすいので個人的にはこっちの方が好みです。

// 認証失敗
{
  "message": "The security token included in the request is invalid.",
  "code": "UnrecognizedClientException",
  "time": "2018-08-19T09:03:53.473Z",
  "requestId": "SVANMBETVBO11DHOKS8A45GRTVVV4KQNSO5AEMVJF66Q9ASUAAJG",
  "statusCode": 400,
  "retryable": false,
  "retryDelay": 41.39762357028852
}

// 認証成功
Created table. Table description JSON: {
  "TableDescription": {
    "AttributeDefinitions": [
      {
        "AttributeName": "title",
        "AttributeType": "S"
      },
      {
        "AttributeName": "year",
        "AttributeType": "N"
      }
    ],
    "TableName": "Movies",
    "KeySchema": [
      {
        "AttributeName": "year",
        "KeyType": "HASH"
      },
      {
        "AttributeName": "title",
        "KeyType": "RANGE"
      }
    ],
    "TableStatus": "CREATING",
    "CreationDateTime": "2018-08-19T09:04:50.440Z",
    "ProvisionedThroughput": {
      "NumberOfDecreasesToday": 0,
      "ReadCapacityUnits": 10,
      "WriteCapacityUnits": 10
    },
    "TableSizeBytes": 0,
    "ItemCount": 0,
    "TableArn": "arn:aws:dynamodb:ap-northeast-1:284677944563:table/Movies",
    "TableId": "b5086ea3-11b7-4f0c-b0aa-933e2be38079"
  }
}

テーブルに初期データをロード

次にサンプルのデータをロードします。WCU=10なので、1件ずつデータをロードするときは理論値としては10 * 1KB 書き込み/sec できるはず。 全部で4612件(3632KB)のJSONを1件ずつArray.forEachで突っ込んでいると、結果的には途中まで進んだところで ProvisionedThroughputExceededException で弾かれるようになりました。200件ほどエラーで弾かれていますね。

$ node MovieLoadData.js
...
PutItem succeeded: Of Human Bondage
Unable to add movie 200 Cartas . Error JSON: {
  "message": "The level of configured provisioned throughput for the table was exceeded. Consider increasing your provisioning level with the UpdateTable API.",
  "code": "ProvisionedThroughputExceededException",
  "time": "2018-08-19T09:21:50.335Z",
  "requestId": "OG9EONR7I6GNIRCSL57FKR8CTRVV4KQNSO5AEMVJF66Q9ASUAAJG",
  "statusCode": 400,
  "retryable": true
}

$ aws dynamodb describe-table --table Movies
# ロードされた項目数が表示される. 
...
"TableSizeBytes": 1991251,
"ItemCount": 4365,
...

CRUD操作

SDKにはCRUD用のAPIが用意されているので、ありがたく使わせてもらいます。 実際のプロジェクトで使うなら生のSDKをそのまま書くんじゃなくて、プロジェクト共通のUtilityとしてラップした関数を作って提供してあげたほうが各開発者には優しいかも。

以下チュートリアルのコードほぼそのままですが、 UpdateExpression, ExpressionAttributeValuesのところが自然言語のようなJPAっぽくて気持ちわるいですね。

const AWS = require("aws-sdk");

// ここのconfigは環境変数で外出ししたほうがベター
AWS.config.update({
  region: "ap-northeast-1",
  endpoint: "https://dynamodb.ap-northeast-1.amazonaws.com"
});

const docClient = new AWS.DynamoDB.DocumentClient()

const table = "Movies";
const year = 2015;
const title = "The Big New Movie";

const params = {
  TableName: table,
  Key: {
    "year": year,
    "title": title
  },
  UpdateExpression: "set info.rating = :r, info.plot=:p, info.actors=:a",
  ExpressionAttributeValues: {
    ":r": 5.5,
    ":p": "Everything happens all at once.",
    ":a": ["Larry", "Moe", "Curly"]
  },
  ReturnValues: "UPDATED_NEW"
};

console.log("Updating the item...");
docClient.update(params, (err, data) => {
  if (err) {
    console.error("Unable to update item. Error JSON:", JSON.stringify(err, null, 2));
  } else {
    console.log("UpdateItem succeeded:", JSON.stringify(data, null, 2));
  }
});

検索操作

  • Query

  • Scan

    • テーブル全体の項目を返す。
    • デフォルトだとテーブル全体で1MB読みこむ。1MB以上あっても1MBしか読みこまない(全部返すんじゃないんかい)
    • 結果セットのフィルタリングができますが、全部読み込んでからフィルタリングで不要なデータを捨てる挙動みたいですね。
    • ベストプラクティスとしては、Limit宣言で読みこむ件数にキャップをかけること。

docs.aws.amazon.com


使ってみた感想としては、AWS上で使うNoSQLとしては面白い選択肢ですね。

テーブルサイズに上限がないとはいえキャパシティユニットのキャパプラをしておかないとすぐスロットルするから、AutoScalingを有効化するのが最初は無難かもしれません。 あとはユースケースを見極めてテーブル設計すればRDBよりも素早いレスポンスで使えそう。

AWSの各サービスを舐める (1) - IAM, EC2, RDS

今年の目標としてAWS 認定デベロッパー - アソシエイト(2018 June)を取ることを目標にしたので、 A CLOUD GURUのコースに沿って各サービスを舐めて回ることにしました。

気になるやつはGetting Startedをなぞって「へー( ˙꒳​˙ )」となるばかりだったので、これを機に使いこなしたいし。 最初の方から割と基本的なことが分かってなかったなと思うことが多いので、しばらくはブログ記事が備忘録になるかと思います笑

今回はタイトルにある、IAM, EC2, RDSのまとめです。

IAM

用語

  • ユーザー

    • AWSを利用する人
  • グループ

    • ユーザーの論理的なまとまり
    • グループに所属するユーザー全員に対してアクセス権限(できること/できないことを規定するもの)を設定・適用できる。
  • ポリシー

    • AWSリソースに対するアクセス権限の設定の論理的なセット
    • 実態はJSON文書
    • AWSで用意されたものだけじゃなく、自分で各リソースへのアクセス権限を設定可能
      • MFAの強制とかは自分でポリシーを作らないとダメっぽい
  • ロール

    • 名前の通り、AWSのアカウントにおける役割のこと。ユーザーまたはAWSリソースに対して付与する。
    • このロールにはポリシーを最大10個までつけることで権限を持つことができる。

ベストプラクティス

  • 個々の利用者にユーザー/アクセスキーを作る

    • ルートキーは漏れるとやばいので、まずは消せ。
    • 1つのアクセスキーは使いまわさない。使い回していると万が一キーを漏れた時に影響範囲が全員になってしんどい。
    • 1User, 1AccessKey / person
  • 権限はロールを使う

    • 同時に言うべきは、アクセスキー/シークレットキーを直接使わないこと
    • ロールに適切な権限をつけることで、ユーザーだけでなくリソースそのものが他のリソースにアクセスするのも安全。
  • 最低限の権限から始める

    • いきなりFull Accessが必要か?
    • 必要に応じて権限をゆるくするのが安全。

EC2

  • 仮想マシン

    • 仮想マシンスペックは汎用的なインスタンスだけでなく、I/O, メモリ, GPU特化のインスタンスもある。
    • OSやM/Wの入ったマシンイメージから作成する。
      • コミュニティイメージにはWAS入りのやつとかもあるので、探せばニッチなやつも見つかりそう。
    • 開発用のマシンが欲しいなら取り敢えずt2.microインスタンスを用意しておけば良さそう。
  • 永続ストレージ(EBS)

    • 自在にEC2に付け外しできるストレージ
    • SSD/HDDから選べる(その中でも更にボリュームサイズやスループットの性能で分類がある)
    • EC2にEBSのストレージをアタッチしてファイルシステムも作れるけど、それならEFS使ったほうが早くね?というのが素直な疑問。
    • 暗号化はもちろん、スナップショットも簡単に取れる。
      • スナップショットから仮想マシンイメージも作れる(!?)

RDS

  • RDBのマネージドサービス
  • 対象は6種類。Aurora以外はFree Teerもあるみたい。

  • 可用性を高めるには、MultiAZ or Backup or SnapShot

  • パフォーマンスを高めるには Read Replica

  • RDSで作られるDBのインスタンスリージョン固有になる。

    • セキュリティグループもリージョンにひもづく
    • EC2等の他のリソースからアクセスする場合はセキュリティグループのInBound/OutBoundで穴を開けておくこと。

次はS3やLambdaあたりを舐めまわそうかな。

AWS Lambda with Node.js を叩いてKubernetesにアプリをデプロイしてみる

Kubernetesへのアプリのデプロイ方法を検討したことがあったのでちょっとまとめてみる。

AWS CodePipelineからKubernetesにビルドしたDockerイメージをデプロイするときは、Lambdaでやるパターンがあるみたいですね。

aws.amazon.com

また、ECSだとCodeDeployでデプロイ先にECSクラスターを選べるので楽チン。ただ、AWSのマネージドサービスではKubernetesを直接選ぶことができなさそうだったので、今回もLambdaで実行する方法を検討してみました。

※ 注釈 -----------------------------------------------

上記のデプロイ方法を検討したのが2018年5月ごろなので、EKSのことは考えていません。 2018年6月初めにAmazon EKSが出てきて、公式ではリソース作成にCloudFormationを多用する方法を用いているので、CodeBuildでCloudFormationのテンプレートを作る --> CodeDeployでCloudFormation経由でデプロイ実行の流れもできそうな気がします。

----------------------------------------------- 注釈 ここまで

いろんな記事を舐めてみた限りクライアントライブラリはPythonのを使うって投稿多いけど、Node.jsでやった例が少なそう。(Node.jsのユースケースとして適しているかは別の話)
ただ、公式のNode.jsのKubernetesクライアントライブラリはドキュメントが貧弱なのが難点です。。。

github.com

クライアントライブラリを使えないとすれば、別のAPIを使う他ない。Kubernetesにはオブジェクト操作用にREST APIが用意されているので、これを利用するやり方にしてます。

どう実装したかソースを見たい方はGitHubのリンクからどうぞ。

github.com

Amazon EKSがGAになったので触った所感

AWSでKubernetesのマネージドサービス Amazon EKS がGAになりました。 今まではAWSでk8sをいじるときはkops経由でクラスターを作って、権限設定してと手間がかかっていましたが、EKSはどれくらい楽になるのかなという観点で触ってみました。

www.atmarkit.co.jp

全体的に

  • AWSのWebコンソール側はめっちゃシンプルで発展途上とさえ感じられるレベル。

    • クラスター情報の参照くらいしかできない最低限のものなので、これからGUIで各種操作できるようにできるのかな?
  • 今の所、使えるのは US West (Oregon) (us-west-2) or US East (N. Virginia) (us-east-1) のみ。

    • 東京リージョン早く来てほしい!
  • リソースの用意は基本はCLI(aws-cli/kubectl) & CloudFormationでやる。

クラスターの作成

  • ネットワーク関連は自分で用意しないといけない。

  • Get Startedの例だとVPC/SecurityGroupをCloudFormation経由でやらせていました。クラスターの作成も含めてCloudFormationでやったほうが早そう...

ノードの作成

  • ワーカーノードも自分で作成する。

    • クラスターとワーカーの紐付けを後から実施する形みたい。Configmapを編集して適用する。
  • Auto Scaling グループを設定してあげるので、最小・最大・希望ノード数の範囲で自動で増減できます。

    • KubernetesのPodのスケールは HolizontalPodAutoscalerで出来るけど、ノードのスケールまで出来るのは可用性を高めるのに良さげ。

Kubernetesにアプリケーションを乗せる

  • 通常のKubernetesの使い方同様に、ReplicationController, Service ect...を作成すればOK。

  • LoadBalancerはアクセスできるようになるまで10minくらい待つので気長にお茶でも飲んで待つ。

ハマりポイント

自分がハマったポイント集。

  • それとMFAでカッチリユーザー権限を設定している人は、事前に aws sts get-session-token --serial-number arn-of-the-mfa-device --token-code code-from-token でtokenを取得すること

    • An error occurred (InvalidClientTokenId) when calling the GetSessionToken operation: The security token included in the request is invalid.
  • もしInvalidClientTokenIdで弾かれる場合はkubeconfigから-r 行および <role-arn> 行を消すと通る。Roleをここで決めておきたいのに。。。

    • could not get token: InvalidClientTokenId: The security token included in the request is invalid.

雑感

  • 認証・認可を上手にIAMとKubernetesのRBACを統合しようとしているなと感じます。AWSでKubernetesを立てるメリットの1つではないでしょうか。

    • ただ、ドキュメントを見る限りEKSでのIAMの立ち位置は"認証"であって、認可はまだRBAC側で設定するみたいです。

    • Kubernetesのリソースアクセスの制御は、ConfigMapにIAMユーザー/ロール/アカウントを追加する必要がありますが、新しいユーザーやロールが追加されるたびに毎度毎度ConfigMapを手で書き換えるのは中々メンテナンスが大変そう。。。ここをGUIや自動化出来るとさらに使いやすいですね。

  • 他にも最初からリソース操作はかなり CloudFormationやkubectl に頼っています。

    • kopsと比較するとVPC/セキュリティグループ/Auto Scaling/Nodeの設定を細かく出来る一方で手作業となる部分が多いので、"とりあえず作って壊す" には少しハードルが高い印象を受けます。

認可の仕組みやVPC、CloudFormationあたりをもう少し探っていきたいと思います。ここら辺が明確になれば、ECSとの差別化要素も見えてきそうです。

npm v6が自動的に脆弱性を見つけるようになった

Node 10.0と合わせて自分のMacのnpmもv6にバージョンアップさせました。 色々いじってた時に脆弱性を教えてくれるようになったので、メモとして残します。

例えばnpm install でnodemonを入れると、以下のように脆弱性があるよと警告を親切に出してくれます。

f:id:accelerk:20180514234607p:plain

画像のインストラクション通り、npm auditで詳細が見られるようです。 素直にコマンドを叩くとこんな図のようにどのパッケージの何がマズイのか表示されるようになります。

f:id:accelerk:20180514234132p:plain

ガイドはNPM公式から出てます。 ガイドを見る限り、npm installを叩くと同時に npm auditも叩かれるみたいです。 もしパッケージインストール時に npm auditを叩かないようにしたい場合は、--no-auditフラグをつければOK。

今回のnodemonの例でやると、以下のようになる。

npm install nodemon --no-audit

また、npmのインストール全体を通して npm auditさせないようにするためには以下のように設定してあげればいいみたいです。

npm set audit false

docs.npmjs.com

Nodeだけじゃなくてnpmも進化してるって発見ですね。 セキュリティ周りをこうやって補強するのを助けてくれるのは嬉しい限りです。最近はGitHubもpackage.jsonから脆弱性アラートを出してくれるようになっているみたいなので、ちゃんとセキュリティ周りは固めておきたいね。

Goで簡易Webアプリ作成&Dockerコンテナにまとめる

ご無沙汰してます。約2ヶ月ぶりの投稿になります。

ここ2ヶ月くらいは社内のイベント向けのアプリを作ったり、Kubernetes勉強会に参加して初めてAWS触ったり、コンテナ周りで新人向けに講師としてスピーカーやったりと技術(特にDockerコンテナ)漬けの日々を過ごしておりました。(Kubernetes@AWSは今度記事にする予定です)

お陰様で今日の健康診断で2ヶ月で10kg体重落ちてました汗
技術だけじゃなく美味しいご飯どころも探さなきゃね。。。

さて、4月末から連休で時間ができたので新しいものに挑戦しようとGoに触り始めました。 折角なら馴染みのあるWebアプリを作成して、Dockerコンテナにまとめるくらいはやろうと全速力でやったので記事投下です。

ソースコード見たいよって方は以下のリンクからどうぞ。

github.com

Go出発点

今まで手を出そうと思いつつ何も手をつけていなかったので、Goについては知識0からスタート。 比較的最近出版された本ならばバージョンの違いでつまづくことは少ないはずなので、とりあえず『スターティングGo言語』買って3日ほどで手を動かしつつ読了。あとは作りたいものを完成させます。

www.shoeisha.co.jp

さっと書店で手に取った本にしてはアタリだった気がします。 全7章建てで、基本で小分けに5章割いて、残りはツールやパッケージの話で2章。1つ1つのGoの仕組みを分解してコード例も沢山載せてくれているので、写経しているだけでもかなり書けるようになります。

Go製のWebアプリを作る

サーバー側は最低限文字列とJSON返すだけ返せればよかったので、簡易なものとしてピッタリな Echo を選択。 公式のドキュメントは分かりやすいし、サイトも見やすいので取っ付きやすかったです。

"minimalist Go web framework" と謳うくらいだから、Node.jsでいうExpress.jsと同じような位置付けなのかな?

echo.labstack.com

以下、公式の例を使いつつルーティングとミドルウェアを設定した main.go 例です。
ルーティングのミドルウェアはデフォルトの設定を使うとJSON形式の見辛いログが出力されるので、ApacheCommon Log Format に似せて出力されるように設定してます。ログ出力は出力内容は柔軟に変えられるみたいですね。

package main

import (
    "net/http"
    "github.com/labstack/echo"
    "github.com/labstack/echo/middleware"
    "github.com/sasaken555/ponz_goecho_server/routes"
)

func main() {
    /* Echoインスタンスの作成 */
    e := echo.New()

    /* Root Level Middleware */
    // ログ出力は Apache Common Log Format っぽく設定すると読みやすい
    e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
        Format: "${host} [${time_rfc3339_nano}] \"${method} ${uri}\" ${status} ${bytes_in} ${bytes_out}\n",
    }))
    e.Use(middleware.Recover())

    /* ルーティングの設定 */
    // 第2引数の値は別パッケージに外出しすると分かりやすい
    e.GET("/users/:id", routes.GetUser)
    e.GET("/users/json", routes.GetJSONUser)

    e.Logger.Fatal(e.Start(":1323")) // ポート1323で起動。
}

また、ルーティングの設定・関数は main に全部突っ込むと後で見づらくなるので、外出ししてあげます。 今回は routes/user.go としてルーティングしたときに返す関数をまとめてみました。

package routes

import (
    "net/http"
    "strconv"
    "github.com/labstack/echo"
    "github.com/sasaken555/ponz_goecho_server/util"
)

// GetUser ... Pathパラメータからユーザー(=ID)を取り出して返す
func GetUser(c echo.Context) error {
    // User ID from Path Parameter `users/:id`
    id := c.Param("id")
    return c.String(http.StatusOK, id)
}

// Customer ... 顧客情報の構造体
type Customer struct {
    ID        int64  `json:"id" xml:"id"`
    Name      string `json:"name" xml:"name"`
    OrderNum  int    `json:"ordernum" xml:"ordernum"`
    OrderProd string `json:"orderprod" xml:"orderprod"`
}

// GetJSONUser ... 顧客情報のJSONを返す
func GetJSONUser(c echo.Context) error {
    userID, err := strconv.ParseInt(c.QueryParam("userId"), 10, 0)  // strconvで文字列から整数に型変換
    userName := c.QueryParam("userName")
    orderNum := util.GetRand(100)  // 別のパッケージからインポートした指定桁数で乱数を返す関数を使う

    // userIDが整数でない(=型変換できない)ならば500エラーを返す
    if err != nil {
        return echo.NewHTTPError(http.StatusInternalServerError, "You Should Provide userId as Integer!")
    }

    // 構造体のポインタを作成
    u := &Customer{
        ID:        userID,
        Name:      userName,
        OrderNum:  orderNum,
        OrderProd: "Blend Coffee",
    }

    return c.JSON(http.StatusOK, u)
}

Dockerコンテナにまとめる

関心があったのと同時に割と引っかかったのが、ここ。特にビルドイメージ大きすぎなのが難点でした。

  • GoはOS依存させないために、OSが標準で備えているようなライブラリを使わず、ランタイムとアプリで使うパッケージを全て実行ファイルの中に取り込みます。この性質があるため、少しコード量の多いアプリでも成果物が数百MBになるのもザラ。

  • 実行ファイルを組み込んだDockerコンテナのイメージがデカイとなると、docker pull の度にディスクと時間が取られるのでポータビリティの点から使いづらい。

上記の問題があったため、ベースイメージを小さいものにするのに加えて、Dockerのマルチステージビルドで対応することしました。

docs.docker.com qiita.com

通常だとソースコードを全てDockerコンテナに含めますが、Goは良くも悪くもソースコードからシングルバイナリ(実行ファイル)を作成するので、実行ファイルだけ入れればOK。下のコード例の(1)が実行ファイル作成のビルド、(2)が(1)で作った実行ファイルをコピーして作った最終系のコンテナイメージになります。

ベースイメージも alpine イメージを使うことでさらに軽量化してます。

# Full SDK version ... (1)
FROM golang:1.10-alpine AS build

RUN apk update && apk upgrade \
    && apk add curl git

RUN curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh

WORKDIR /go/src/github.com/sasaken555/ponz_goecho_server
COPY . .

RUN dep ensure
RUN go build -o ponz_goecho_server

# Final Output ... (2)
FROM golang:1.10-alpine
COPY --from=build /go/src/github.com/sasaken555/ponz_goecho_server/ponz_goecho_server /bin/ponz_goecho_server
CMD /bin/ponz_goecho_server

結果として、通常のベースイメージ+アプリソースを含めてビルドしたイメージ(tag: heavy)とalpineベースイメージ+マルチステージビルドのイメージでサイズは半分以下に抑えられています!やったね!

$ docker image ls ponz_goecho_server
REPOSITORY           TAG                 IMAGE ID            CREATED              SIZE
ponz_goecho_server   light               24b61e6c4f83        7 seconds ago        386MB
ponz_goecho_server   heavy               20bac15a0acf        About a minute ago   908MB

新しい技術(今回は言語でしたが)として今回はGoを選択しましたが、謳い文句の通りシンプルかつ効率的に開発&ビルドできたのはすごい...!!
バイナリ単位でアプリはまとめられるので、マイクロサービスと相性が良さそうな印象です。しかもブロックチェーンのHyperledger fabricもGoで開発できるようなので、次はGoを使ってブロックチェーンに挑もうかなと夢が膨らんだところで締めます。