しがないソフトウェアエンジニアの探求日誌

六本木一丁目で働くソフトウェアエンジニアのブログ

「PHPフレームワーク Laravel Webアプリケーション開発」を読み終えた感想

2018/9/26に発売された、書籍「PHPフレームワーク Laravel Webアプリケーション開発」を読み終えましたので、感想を書いていきます。

PHPフレームワーク Laravel Webアプリケーション開発」とは

f:id:khigashigashi:20181007232928j:plain https://www.amazon.co.jp/gp/product/4802611846

こちらは、PHP界隈ではとても有名な@ex_takezawaさん・@kurikazuさん・@shin1x1さん・@omoonさんが執筆し2018年9月26日に発売されたものになります。 書籍の概要については、@shin1x1さんのブログにてご紹介されている内容を参照させていただきます。

blog.shin1x1.com

Laravel 5.5(LTS) に対応した本です。Laravel リファレンス のようにフレームワークの機能解説はもちろん含まれているのですが、機能を紹介するというより、実際にありそうなユースケースでどのように利用するかといった視点で書かれており、開発現場で Laravel をより活用したい向けの内容になっています。目次を見ると、実践的な内容が多く含まれていることが分かるでしょう。

読み始める前のモチベーション

今回の本を手にとったモチベーションとしては次のような物がありました。

  • 普段、CakePHPを業務で触っているが、Laravel自体を改めてがっつり触りたい
  • サービスコンテナがどのような実装になっているかなどLaravel内部の実装を知りたい
  • レイヤードアーキテクチャなどアーキテクチャについての実践例を知りたい

読んだ結果

控えめに言って大変勉強になった内容でした。具体的には下記の点がとても良かったです。

Laravelの内部実装・構造から学ぶ

序盤の章にて、Laravelの内部実装を見ながら、サービスコンテナの仕組みやMVCADRといったアーキテクチャについて学べる内容になっていました。自分の中で言語化されていなった点についてLaravelのコードにて示される点がよかったです。

テスト駆動開発の実践例

最終章にて、顧客訪問記録アプリケーションを想定したバックエンドAPIをテスト駆動で実装してく章があります。
API実装といった業務で実践的な例でのテスト駆動開発についてやり方を見ることができたのは、普段自分が実践してみている方法と照らし合わせつつ見れたのでとても参考になりました。

読み方

この著書は、合計531ページありとてもボリューミーだったので、私は次の読み方で進めました。

  • 第一部は、全部しっかり読み込み・写経する
  • 第二部は、部分的に詳細の仕様の話になるので、全体感を抑えながら第一部からつながる部分のみ写経する
  • 第三部は、テスト駆動開発での実践で第一部・第二部の知識の総集編として抑えられるのでガッツリ写経する

写経レポジトリ

今回写経していったレポジトリはgithub上にcommitしながらやっていました。chapter終了タイミングごとにcommitをしてrelease labelを付ける方法でやっています。

github.com

最後に

PHPフレームワーク Laravel Webアプリケーション開発」は、普段Laravelを使っていない身でも理解しやすく、Laravelに限らずアプリケーション設計・アーキテクチャなど非常に汎用的な知識に関して実践できる内容になっていました。

まだ、手に取られていない方は、ぜひ一度手に取るといいと思います!

Amazon CAPTCHA

(サインほしい。。。)

ドメイン駆動設計についてpospomeさんの話を聞いた | Gopher道場課外学習Ⅰ参加ログ

Gopher道場卒業生向けのイベントとして、ドメイン駆動設計について@pospomeさんの話を聞かせていただきました。

mercari.connpass.com

その際に話されていた内容についてざっとメモを残させていただきます。

ドメイン駆動設計とは

  • ドメイン
    • = 問題領域
    • ソフトウェアを適用する業務や関心事
    • ドメイン = 業務
  • 例:弥生会計
  • 業務で設計を駆動すること = ドメイン駆動設計

    merit of ddd

  • 既存の業務駆動ではない設計手法
    • DOA(データ中心アプローチ)
      • テーブル構造がModelになるMVCフレームワークではDOAになりがち
      • DBにデータを格納することを前提としている
      • -> DBの特性・目的に合わせたデータ設計になる
        • 特性:redis・RDBMS等でデータ設計が異なる
        • 目的:頻度・データ量によって異なる
      • DOAによる設計はプログラミングに対して最適かどうかはそうとは言えない、可読性の低下の可能性
        • 業務上存在する概念をコード上で扱えない
        • ex: UserAcountテーブルの構造体とプロフィール変更
          • プロフィール変更についてどのカラムがプロフィールなの?ということに関して表現できていない
          • UserProfile structを作って何がプロフィールかをコード上で表現する
        • ex: Boss
          • rankをもっている、big: 大ボス、middle: 中ボス
          • 大ボスのみAttack()できるという仕様をどう表現するか、Attackはいつ発動するのか(中ボスからAttackを呼んだときになにもしないという事がわからない。)
          • BigBoss / MiddleBossでそもそも分けてしまう。別々のモデルにすることでそれぞれ何ができるかが明確になる
  • DOAで設計したデータ構造をそのままプログラミングで扱うのが問題
  • 可読性が下がる根本原因 = 業務とコードの乖離
    • 問題:業務の理解・コードの理解、両方を理解しないといけない
      • ex: type, status, flg といった謎カラム
  • DDDではデータ構造をそのままプログラミングで利用しない、プログラミングに適したデータ構造を利用する
    • -> 業務とコードの乖離を可能な限り少なくし可読性を分ける
    • -> 業務を理解 -> コードも理解できる
    • -> 実装詳細に踏み込みやすいコードになる
  • 業務を反映したデータ構造 + 振る舞い => 業務駆動開発(DDD(

何をすればDDDになるのか

  • 実装がゴール、実装のための分析・設計
  • 分析
    • 業務をコードに落とし込めるように分析する
    • 実装がゴール、そのゴールを達成するための分析
  • 設計
    • 分析結果をコードに落とし込むための設計
  • 実装
    • 実装する
  • 各フェーズに対応したテクニックが存在する
    • レイヤ構造は実装のための設計、分析・設計・実装のための一つの要素に過ぎない。
    • リポジトリパターンは実装テクニック、同上でただの一つの要素に過ぎない
  • 戦略(分析&設計)・戦術とDDDは分けている
  • 戦略(分析&設計)が大事
  • 戦術のみ適用する = 軽量DDD、アンチパターン
    • ex. レイヤ構造・リポジトリパターンを適用したよ = 軽量DDD
    • 分析をしていない、業務とコードに乖離が生じる
  • 分析→設計→実装を通して初めてDDDになる

demerit of ddd

  • 学習コスト
  • 分析コスト
  • 実際可読性が上がるかはわからない、分析結果によっては可読性が上がらない可能性がある
  • モデルとDBのデータ構造が異なる、モデルとDBがほぼ同一である前提であるRailsのようなフレームワークを使っている場合の実装の手間

解決できないこと

  • 業務の複雑さに伴うコードの複雑さは解決できない
    • そもそも業務が複雑であれば、それをそのまま反映する
  • 実装者の設計スキルが低いと可読性落ちる

necessity of DDD

DDD不要な例

  • DBのデータ構造がプログラミングに適している場合
    • ex: 単一概念のCRUD
    • DDD不要
  • 業務とコードのか入が問題にならない
    • 保守しない、業務が単純

軽量DDD

  • DDDではないが、単なる実装パターン詰合せとしては優秀

GoとDDDの相性

GoでDDDは相性

  • ORM問題 Java > Go
    • モデルとデータベースのマッピング
    • ORMでマッピングを任せられると
    • インピナンスミスマッチを埋められるか
      • Goにはない、というか他の言語にもあまりない、Java特有かもしれない
  • アノテーションベースのAOP Java > Go
  • 情報量 Java/Scala > Go
    • Javaの実践例はPHP/Rubyでも参考になる
    • Goはすこし言語仕様が異なるので実践した情報量が少ない

Q&A

  • メルカリで使っているか
    • 現状使っていない、ビジネススピードなどによりWeb系で導入していない
  • microserviceとの相性
    • 相性はいいっちゃいい、業界付けられたコンテキストのテクニックは相性がいい
    • ただし、DDDとマイクロサービスの設計の親和性は要件による。
      • データ構造がプログラミングに適しているかどうか。
    • 密結合を分割 -> 分断されたモノリス
  • マイクロサービス間で重複したコードを書く問題
    • マイクロサービスをやるならコードの重複を受け入れるのが前提のスタンスと必要になる
    • その上で共有したい場合は、ライブラリ化・サブモジュール化というアプローチもあるが、各サービスで必要なメソッド・フィールドを足していったりするとカオスになる。
    • サービスごとに必要とすることが異なるケースが多い。
  • コードが成果物 = コードを見ることで業務を知ることができる
    • DDDの目的は可読性の高いコードを書くこと
  • いい設計を行うためのアプローチ
    • 作る対象の業務を知る必要がある
    • 業務の理解度を上げることでコードの質が上がる
    • ただし、設計の引き出しが多い人が勝つ、設計スキルが低いとそもそも可読性が下がる
    • たくさん書いてたくさん失敗すること
    • なれている自分の設計パターンと違うアプローチを取り入れてみる、いつものパターンは人から見たら良くないかもしれない。

最後に

ドメイン駆動設計について、何をやったら「ドメイン駆動設計」なの?とふわふわしていたのですが、「業務(ドメイン)で設計を駆動する」がどういうことなのか解説いただいたのがとても参考になりました。

次回はGCPについてのお話が聞けるということでとても楽しみです! mercari.connpass.com

OAuth 2.0 サーバの実装・テストについてgolangtokyoで発表してきました | golang.tokyo#18 参加レポート

2018年9月28日(金)に開催されたgolang.tokyo #18に参加して、LT枠(10分)で発表してきました。

golang.tokyoとは

golangtokyo.connpass.com

プログラミング言語のGoの導入企業のメンバーが集まり、Goの普及を推進するコミュニティです トークイベント、ハンズオン、etcのイベントを開催していく予定です!

発表内容

今回は、APIを作成するに当たり面倒で避けられない「認証・認可」について、各種背景からモノリシックにリクエストハンドラーとして組み込む場合どう実装するかという発表でした。
フルスクラッチではなく、Red Hat OpenShiftgithubに公開している、openshift/osinというOAuth2.0のサーバーライブラリを用いた実装・テストの例をまとめています。

LT応募方法

LT応募してみたいという方は、毎回のconnpassページ内で以下の感じでLT募集フォームがあると思うのでこちらで応募できます!

f:id:khigashigashi:20181001090020p:plain

感想

約1年前からGo言語を趣味で触り始めてどこかでgolang.tokyoで発表してみたいと思っていたので、まだまだ精進が必要ですが一つ目標が達成できてよかったです。また、ネタを見つけて発表しに行きたいと思います。

TerraformでAWSのインフラ構成構築を自動化する(入門)

Terraformを用いてAWSのインフラ構成・構築を自動化するに当たり入門資料です。この記事では、Amazon Web Services 基礎からのネットワーク&サーバー構築 改訂版で構築する構成図をTerraformで構築していきます。

発表資料

Terraformとは

Terraformとは、HashiCorpが作っているコードからインフラリソースを作成・管理するためのツールです。

www.terraform.io

AWS, GCP, Azure, Heroku など多くのSaaSに幅広く対応しています。

f:id:khigashigashi:20180925213924p:plain

今回作成するインフラ構成

Amazon Web Services 基礎からのネットワーク&サーバー構築 改訂版で構築する構成がベースになります。

f:id:khigashigashi:20180925220502p:plain

具体的には、次のような構成要素が存在します。

  • VPC
    • CIDR: 10.1.0.0/16
    • Internet gatewayがattachされている
  • public subnet
    • CIDR: 10.1.1.0/24
    • 外部InternetからのINBOUND接続が可能なsubnet
  • private subnet
    • CIDR: 10.1.2.0/24
    • 外部InternetからのINBOUND接続が遮断されたsubnet
    • OUTBOUND接続は可能とするため、NAT Gatewayに対してRoutingが設定されている
  • EC2 instance: "web"
    • subnet: public subnet
    • private IP address: 10.1.1.10
    • 外部Internetからの接続を受け付けるようなWebサーバーが想定される
  • EC2 instance: "db"
    • subnet: private subnet
    • private IP address: 10.1.2.10
    • 外部Internetからの接続を受け付けないDBサーバーが想定される

構築する

今回構築する際のサンプルコードは下記レポジトリにて公開しています。

github.com

準備

本資料を進めるに当たり、Terraformがinstallされている必要があるため公式リファレンスにそってインストールしておきましょう。

www.terraform.io

手順

  1. Asia/PacificにVPCを作成する
  2. public subnetを作成する
  3. Internet Gatewayを作成してpublic subnetのroute tableに設定する
  4. public subnetにEC2 instanceを作成する
  5. private subnetを作成しEC2 instanceを作成する
  6. NAT Gatewayを作成する

1. Asia/PacificにVPCを作成する

本章のコードは、GitHub - Khigashiguchi/terraform-basic-network at 0.1 にて公開しています。

Terraformでは、*.tf拡張子のファイルでインフラ構成について定義していきます。まずは、VPCを作成するところからです。main.tfというファイルを作ります。

provider "aws" {
  access_key = "${var.aws_access_key}"
  secret_key = "${var.aws_secret_key}"
  region = "ap-northeast-1"
}

まず、最初のproviderでは、どのSaaSを利用したインフラ構成について定義していくのかを書きます。ここが、Google Cloudであればgoogleと定義することになります。

Provider: Google Cloud - Terraform by HashiCorp

今回は、AWSを利用するため、awsをproviderに指定し、AWSAPIに利用に必要なaccess_keysecret_key、そして対象regionを指定します。
ここでは、べた書きすることも可能なのですがgit管理外にアクセスキーを置きたいため、variablesという機能を用います。

別途、variable.tfというファイルを作成して次のように定義します。

variable "aws_access_key" {}
variable "aws_secret_key" {}

上記のを指定することで、main.tf内では"${var.aws_access_key}"といった形で使うことが出来ます。ここでvariableとした場合、実際にインフラ構築時のコマンドインターフェースにて値を入力するなどと行ったgit管理外の場所から注入することが可能になります。

この手順では、VPCを作成したいため先程定義したmain.tfに対して以下追記します。

provider "aws" {
  access_key = "${var.aws_access_key}"
  secret_key = "${var.aws_secret_key}"
  region = "ap-northeast-1"
}

 // +以下追記
resource "aws_vpc" "main" {
  cidr_block = "10.1.0.0/16"

  tags {
    Name = "VPC領域:Terraform"
  }
}

実際に実行してみましょう。まず、terraform initというコマンドによって初期化します。

$ terraform init

その後、terraform applyというコマンドによって記述したインフラ構成を実際に構築することが出来ます。

$ terraform apply

var.aws_access_key / var.aws_secret_key

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + aws_vpc.main
      id:                               <computed>
      arn:                              <computed>
      assign_generated_ipv6_cidr_block: "false"
      cidr_block:                       "10.1.0.0/16"
      default_network_acl_id:           <computed>
      default_route_table_id:           <computed>
      default_security_group_id:        <computed>
      dhcp_options_id:                  <computed>
      enable_classiclink:               <computed>
      enable_classiclink_dns_support:   <computed>
      enable_dns_hostnames:             <computed>
      enable_dns_support:               "true"
      instance_tenancy:                 "default"
      ipv6_association_id:              <computed>
      ipv6_cidr_block:                  <computed>
      main_route_table_id:              <computed>
      tags.%:                           "1"
      tags.Name:                        "VPC領域:Terraform"


Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value:

コマンドを実行した後、Enter a valueと聞かれて一度止まるので、ここでyesと入力することで実際に動作します。 コマンド実行終了後、現状どうなっているかを確認するためには、terraform showというコマンドを実行することで確認することが出来ます。

$ terraform show

ここでは、確認できたので一度作成したVPCを削除してみます。削除するには、terraform destroyと実行することで構築したものを削除することが出来ます。

$ terraform destroy

次に、作成したVPC内でsubnetを切っていきます。

手順1の関連資料

2. public subnetを作成する

本章のコードは、GitHub - Khigashiguchi/terraform-basic-network at 0.2 にて公開しています。

作成したVPC内にsubnetを切ります。手順1で作成したmain.tfに次の記述を追加することでsubnetを作成することが出来ます。

provider "aws" {
  access_key = "${var.aws_access_key}"
  secret_key = "${var.aws_secret_key}"
  region = "ap-northeast-1"
}

resource "aws_vpc" "main" {
  cidr_block = "10.1.0.0/16"

  tags {
    Name = "VPC領域:Terraform"
  }
}

// + 以下追記
resource "aws_subnet" "web" {
  vpc_id = "${aws_vpc.main.id}"
  cidr_block = "10.1.1.0/24"

  tags {
    Name = "Public Subnet by Terraform"
  }
}

ここでは、CIDRが10.1.1.0/24であるsubnetをVPC内に作成しています。次に作成したsubnetに対してroute tableを作成していきます。

手順2の関連資料

3. Internet Gatewayを作成してpublic subnetのroute tableに設定する

本章のコードは、GitHub - Khigashiguchi/terraform-basic-network at 0.3 にて公開しています。

手順2で作成したsubnetのroute table・外部internetからの接続を受け付けるためのinternet gatewayを作成します。main.tfに次のように追記します。

provider "aws" {
  access_key = "${var.aws_access_key}"
  secret_key = "${var.aws_secret_key}"
  region = "ap-northeast-1"
}

resource "aws_vpc" "main" {
  cidr_block = "10.1.0.0/16"

  tags {
    Name = "VPC領域:Terraform"
  }
}

// + 追記
resource "aws_internet_gateway" "gw" {
  vpc_id = "${aws_vpc.main.id}"

  tags {
    Name = "Internet Gateway by Terraform"
  }
}

// + 追記
resource "aws_route_table" "r" {
  vpc_id = "${aws_vpc.main.id}"

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "${aws_internet_gateway.gw.id}"
  }

  tags{
    Name = "Public route table by Terraform"
  }
}

resource "aws_subnet" "public" {
  vpc_id = "${aws_vpc.main.id}"
  cidr_block = "10.1.1.0/24"

  tags {
    Name = "Public Subnet by Terraform"
  }
}

// + 追記
resource "aws_route_table_association" "a" {
  subnet_id = "${aws_subnet.public.id}"
  route_table_id = "${aws_route_table.r.id}"
}

ここでは、3つの要素が追記されています。
まず、AWS Internet GatewayVPC内に作成しています。そして、作成したInternet Gatewayに接続を流すroute tableを作成しています。最後に、public subnetとroute tableを関連付けています。
これにより、"public subnet"は外部Internetからの接続を受け付けられるようになりました。

手順3の関連資料

4. public subnetにEC2 instanceを作成する

本章のコードは、GitHub - Khigashiguchi/terraform-basic-network at 0.4 にて公開しています。

手順3までで作成したsubnet内にEC2 instanceを作ります。main.tfに追記します。

provider "aws" {
  access_key = "${var.aws_access_key}"
  secret_key = "${var.aws_secret_key}"
  region = "ap-northeast-1"
}

resource "aws_vpc" "main" {
  cidr_block = "10.1.0.0/16"
  enable_dns_support = true // +追記
  enable_dns_hostnames = true // +追記

  tags {
    Name = "VPC領域:Terraform"
  }
}

resource "aws_internet_gateway" "gw" {
  vpc_id = "${aws_vpc.main.id}"

  tags {
    Name = "Internet Gateway by Terraform"
  }
}

resource "aws_route_table" "r" {
  vpc_id = "${aws_vpc.main.id}"

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "${aws_internet_gateway.gw.id}"
  }

  tags{
    Name = "Public route table by Terraform"
  }
}

resource "aws_subnet" "public" {
  vpc_id = "${aws_vpc.main.id}"
  cidr_block = "10.1.1.0/24"

  tags {
    Name = "Public Subnet by Terraform"
  }
}

resource "aws_route_table_association" "a" {
  subnet_id = "${aws_subnet.public.id}"
  route_table_id = "${aws_route_table.r.id}"
}

// + 追記
resource "aws_security_group" "wsg" {
  name = "web-sg"
  description = "web-sg security group created by terraform"
  vpc_id = "${aws_vpc.main.id}"

  ingress{
    from_port = 22
    to_port = 22
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress{
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags{
    Name = "web-sg"
  }
}

// + 追記
resource "aws_key_pair" "auth" {
  key_name = "${var.key_name}"
  public_key = "${file(var.public_key_path)}"
}

// + 追記
resource "aws_instance" "web" {
  ami = "ami-08847abae18baa040" // Amazon Linux 2 AMI (HVM), SSD Volume Type
  instance_type = "t2.micro"
  subnet_id = "${aws_subnet.public.id}"
  private_ip = "10.1.1.10"
  associate_public_ip_address = true
  security_groups = [
    "${aws_security_group.wsg.id}"
  ]
  key_name = "${aws_key_pair.auth.id}"

  tags {
    Name = "Web Server by Terraform"
  }
}

ここでは、3つの要素を追加しています。
まずは、Instanceに設定するSecurity Groupです。web-sgという名前で作っています。このセキュリティグループは、22・80ポートを全IPから受け付けるという設定になります。
次に、aws_key_pairという公開鍵認証でSSH接続する際の鍵です。ここでは次の実施する内容にてローカルで生成した公開鍵をkey pairに指定するという作業を進めます。
最後に、EC2 instanceを作成しています。大まかに下記のような内容のInstanceを作成します。

  • 無料枠で使用できるAMI(Amazon Linux 2 AMI (HVM), SSD Volume Type_
  • インタンスタイプ:t2.micro
  • subnet:上で作成したpublic subnet

そして、SSH接続するためのキーを作成・設定します。まず、すでに定義したmain.tfの記述内容に合わせてvariables.tfに下記を追加します。

variable "aws_access_key" {}
variable "aws_secret_key" {}
// +追記
variable "key_name" {
  description = "the name of aws key pair"
}
// +追記
variable "public_key_path" {
  description = "path to the ssh public key"
}

そして、これまでvariableの指定をコマンドインターフェースからやっていましたが、同一ディレクトリにtfvarsという拡張子のファイルを置くことによって、ファイルに指定することが出来ます。
実際に、terraform.tfvarsというファイルを作成して下記のように定義します。

aws_access_key = "YOUR-ACCESS-KEY"
aws_secret_key = "YOUR-SECRET-KEY"

key_name = "terraform-basic-key"
public_key_path = "~/.ssh/terraform-basic-key.pub"

今まで、コマンドインターフェースで入力していた内容もこのように書くことにってファイルからの入力が可能になります。(このファイルはシークレット情報が含まれるためgit管理外に置くことが推奨されます。)

最後に、terraform-basic-keyという名前でsshキーを作成して~/.ssh以下に設置しておきましょう。

上記により、EC2 Instanceの生成が可能になりました。

手順4の関連資料

5. private subnetを作成しEC2 instanceを作成する

本章のコードは、GitHub - Khigashiguchi/terraform-basic-network at 0.6 にて公開しています。

さらに、外部Internetからの接続を受け付けないprivate subnetを作成してその中にinstanceを作ります。main.tfに追記していきます。

provider "aws" {
  access_key = "${var.aws_access_key}"
  secret_key = "${var.aws_secret_key}"
  region = "ap-northeast-1"
}

resource "aws_vpc" "main" {
  cidr_block = "10.1.0.0/16"
  enable_dns_support = true
  enable_dns_hostnames = true

  tags {
    Name = "VPC領域:Terraform"
  }
}

resource "aws_internet_gateway" "gw" {
  vpc_id = "${aws_vpc.main.id}"

  tags {
    Name = "Internet Gateway by Terraform"
  }
}

resource "aws_route_table" "r" {
  vpc_id = "${aws_vpc.main.id}"

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "${aws_internet_gateway.gw.id}"
  }

  tags{
    Name = "Public route table by Terraform"
  }
}

resource "aws_subnet" "public" {
  vpc_id = "${aws_vpc.main.id}"
  cidr_block = "10.1.1.0/24"
  availability_zone = "ap-northeast-1d"

  tags {
    Name = "Public Subnet by Terraform"
  }
}

 // +追記
 // private subnetの作成、internet gatewayとの接続のないdefault route tableをそのまま使うことで外部Internetから遮断されている状態にする
resource "aws_subnet" "private" {
  vpc_id = "${aws_vpc.main.id}"
  cidr_block = "10.1.2.0/24"
  availability_zone = "ap-northeast-1d"

  tags {
    Name = "Private Subnet by Terraform"
  }
}

resource "aws_route_table_association" "a" {
  subnet_id = "${aws_subnet.public.id}"
  route_table_id = "${aws_route_table.r.id}"
}

resource "aws_security_group" "wsg" {
  name = "web-sg"
  description = "web-sg security group created by terraform"
  vpc_id = "${aws_vpc.main.id}"

  ingress{
    from_port = 22
    to_port = 22
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress{
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  // +追記
  // pingでの疎通確認ができるように設定
  ingress{
    from_port = -1
    to_port = -1
    protocol = "icmp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags{
    Name = "web-sg"
  }
}

 // +追記
 // 22番・3306番ポート・ICMPプロトコル通信を許可する設定(DBサーバを想定しているため)
resource "aws_security_group" "dsg" {
  name = "db-sg"
  description = "db-sg security group created by Terraform"
  vpc_id = "${aws_vpc.main.id}"

  ingress{
    from_port = 22
    to_port = 22
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress{
    from_port = 3306
    to_port = 3306
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress{
    from_port = -1
    to_port = -1
    protocol = "icmp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_key_pair" "auth" {
  key_name = "${var.key_name}"
  public_key = "${file(var.public_key_path)}"
}

resource "aws_instance" "web" {
  ami = "ami-08847abae18baa040" // Amazon Linux 2 AMI (HVM), SSD Volume Type
  instance_type = "t2.micro"
  subnet_id = "${aws_subnet.public.id}"
  private_ip = "10.1.1.10"
  associate_public_ip_address = true
  security_groups = [
    "${aws_security_group.wsg.id}"
  ]
  key_name = "${aws_key_pair.auth.id}"

  tags {
    Name = "Web Server by Terraform"
  }
}

// +追記
// private subnet内にinstanceを作成
resource "aws_instance" "db" {
  ami = "ami-08847abae18baa040" // Amazon Linux 2 AMI (HVM), SSD Volume Type
  instance_type = "t2.micro"
  subnet_id = "${aws_subnet.private.id}"
  associate_public_ip_address = false
  private_ip = "10.1.2.10"
  security_groups = [
    "${aws_security_group.dsg.id}"
  ]
  key_name = "${aws_key_pair.auth.id}"

  tags {
    Name = "DB Server by Terraform"
  }
}

行数が増えてきたので、main.tfのコメントにて詳細について記載しています。

6. NAT Gatewayを作成する

本章のコードは、GitHub - Khigashiguchi/terraform-basic-network at 0.7 にて公開しています。

private subnet内のInstanceから外部に対してのOUTBOUND通信ができるようにNAT Gatewayを設定します。main.tfに下記のように追記します。

provider "aws" {
  access_key = "${var.aws_access_key}"
  secret_key = "${var.aws_secret_key}"
  region = "ap-northeast-1"
}

resource "aws_vpc" "main" {
  cidr_block = "10.1.0.0/16"
  enable_dns_support = true
  enable_dns_hostnames = true

  tags {
    Name = "VPC領域:Terraform"
  }
}

resource "aws_internet_gateway" "gw" {
  vpc_id = "${aws_vpc.main.id}"

  tags {
    Name = "Internet Gateway by Terraform"
  }
}

resource "aws_route_table" "public" { // - 修正 from "a" -> "public"
  vpc_id = "${aws_vpc.main.id}"

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "${aws_internet_gateway.gw.id}"
  }

  tags{
    Name = "Public route table by Terraform"
  }
}

// + 追記
// private subnetのroute tableを作成、以降で定義するNAT Gatewayを指定。
resource "aws_route_table" "private" {
  vpc_id = "${aws_vpc.main.id}"

  route {
    cidr_block = "0.0.0.0/0"
    nat_gateway_id = "${aws_nat_gateway.default.id}"
  }

  tags{
    Name = "Private route table by Terraform"
  }
}

resource "aws_subnet" "public" {
  vpc_id = "${aws_vpc.main.id}"
  cidr_block = "10.1.1.0/24"
  availability_zone = "ap-northeast-1d"

  tags {
    Name = "Public Subnet by Terraform"
  }
}

resource "aws_subnet" "private" {
  vpc_id = "${aws_vpc.main.id}"
  cidr_block = "10.1.2.0/24"
  availability_zone = "ap-northeast-1d"

  tags {
    Name = "Private Subnet by Terraform"
  }
}

// + 追記
// NAT Gateway用のEIPを作っておく
resource "aws_eip" "nat" {
  vpc = true
}

// + 追記
// NAT Gatewayを作成
resource "aws_nat_gateway" "default" {
  allocation_id = "${aws_eip.nat.id}"
  subnet_id = "${aws_subnet.public.id}"

  tags{
    Name = "default Gateway by Terraform"
  }
}

resource "aws_route_table_association" "public" { // - 修正 from "a" -> "public"
  subnet_id = "${aws_subnet.public.id}"
  route_table_id = "${aws_route_table.public.id}"
}

// + 追記
// private subnetと追加作成したroute tableを関連付ける
resource "aws_route_table_association" "private" {
  subnet_id = "${aws_subnet.private.id}"
  route_table_id = "${aws_route_table.private.id}"
}

resource "aws_security_group" "wsg" {
  name = "web-sg"
  description = "web-sg security group created by terraform"
  vpc_id = "${aws_vpc.main.id}"

  ingress{
    from_port = 22
    to_port = 22
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress{
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress{
    from_port = -1
    to_port = -1
    protocol = "icmp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags{
    Name = "web-sg"
  }
}

resource "aws_security_group" "dsg" {
  name = "db-sg"
  description = "db-sg security group created by Terraform"
  vpc_id = "${aws_vpc.main.id}"

  ingress{
    from_port = 22
    to_port = 22
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress{
    from_port = 3306
    to_port = 3306
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress{
    from_port = -1
    to_port = -1
    protocol = "icmp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_key_pair" "auth" {
  key_name = "${var.key_name}"
  public_key = "${file(var.public_key_path)}"
}

resource "aws_instance" "web" {
  ami = "ami-08847abae18baa040" // Amazon Linux 2 AMI (HVM), SSD Volume Type
  instance_type = "t2.micro"
  subnet_id = "${aws_subnet.public.id}"
  private_ip = "10.1.1.10"
  associate_public_ip_address = true
  security_groups = [
    "${aws_security_group.wsg.id}"
  ]
  key_name = "${aws_key_pair.auth.id}"

  tags {
    Name = "Web Server by Terraform"
  }
}

resource "aws_instance" "db" {
  ami = "ami-08847abae18baa040" // Amazon Linux 2 AMI (HVM), SSD Volume Type
  instance_type = "t2.micro"
  subnet_id = "${aws_subnet.private.id}"
  associate_public_ip_address = false
  private_ip = "10.1.2.10"
  security_groups = [
    "${aws_security_group.dsg.id}"
  ]
  key_name = "${aws_key_pair.auth.id}"

  tags {
    Name = "DB Server by Terraform"
  }
}

手順6の関連資料

上記設定追加により、NAT Gatewayが作成されました。以上にて、目標としていたインフラ構成の実現が完了しました。 f:id:khigashigashi:20180925220502p:plain

まとめ

  • Terraformを用いてAWSでのインフラ構築を行いました。インフラ構成をコード化しておくと再度作成する時や不要になった際の削除などが用意になるため、業務インフラ構成や個人でのインフラ構築などにも有用になるかと思います。

湯河原「おんやど恵」で足湯駆動開発 | 開発合宿レポート

2018年9月1日(土)〜2018年9月3日(月)の2泊3日で、友人と湯河原まで開発合宿してきましたので、雑にどんな感じの場所かを書いていきます。

f:id:khigashigashi:20180903113700j:plain
足湯駆動開発

宿

「おんやど恵」という旅館に2泊3日で泊まりました。

www.onyadomegumi.co.jp

Googleで「開発合宿」と調べるとすぐ検索で出てくる有名な宿です。

f:id:khigashigashi:20180903114318p:plain

部屋は和室のとても快適なお部屋です。

f:id:khigashigashi:20180903113842j:plain
部屋

開発場所

専用の会議室を予約することができます。

f:id:khigashigashi:20180903113849j:plain
会議室「両国」

また、足湯コーナーがあるので定期的に足湯駆動開発しました。

ご飯

旅館では、朝・夜ごはんを用意していただけます。

夜ご飯は、会席料理をいただきました。

f:id:khigashigashi:20180903113814j:plain
夜ご飯のステーキ

朝ごはんもボリュームが有って美味しいです。

f:id:khigashigashi:20180903113835j:plain
朝ごはん

駅周辺グルメ

旅館付近にはあまりないですが、湯河原駅までいくと、「らーめん 麺の蔵」というラーメン屋があります。

f:id:khigashigashi:20180903113902j:plain
らーめん 麺の蔵

f:id:khigashigashi:20180903113910j:plain
醤油チャーシューラーメン

旅館付近では、旅館のすぐ横にLAWSONがあったのでそこで昼ごはんなどを買っていました。

ちょっと気分転換したい時

少し歩けばとてもマイナスイオンが感じられる公園が出てきます。

f:id:khigashigashi:20180903113828j:plain
万葉公園

歩いて10分くらいの距離にありました。

また、独歩の湯という足湯施設に行くことも出来ます。

www.yugawara.or.jp

18:00閉店ですが、気分転換に遊び行ってもいいですね。

f:id:khigashigashi:20180903113821j:plain
独歩の湯

まとめ

9月ということもあり、人が多すぎることもなくとても快適に過ごせました。

ECS(Fargate)で動かすコンテナにSSMからクレデンシャル情報を渡す

発表時の資料

本記事に関する発表資料はこちらです。

目次

  • クレデンシャル情報の扱い方
  • 概要構成
  • 具体的な手順
  • 参考資料

クレデンシャル情報の扱い方

クレデンシャル情報の扱い方を考えるに当たり、Beyond the Twelve-Factor Appというクラウドネイティブアプリケーションの設計パターンについて説明した資料がよく参照されています。

上記の資料内の「05. CONFIGURATION, CREDENTIALS, AND CODE」という章で次のように記載されています。

f:id:khigashigashi:20180828170659p:plain

「今すぐにでもコードをオープンソース化できるかどうか」というのがわかりやすい指標ですね。こちらで推奨されている環境変数への格納を今回進めていきます。

寄り道:環境変数への格納について検討

環境変数への格納について、「セキュリティ観点」・「管理観点」の両者で検討してみます。

セキュリティ観点

セキュリティ観点的には、必要以上に参照可能な状態を避ける必要があります。例えば、「/etc/environmentにクレデンシャル情報を書いてそれを使う」といったことをした場合は、アプリケーションの動作に必要なプロセス以外からもクレデンシャル情報を読むことができます。
今回のケースでは、コンテナプロセス上でアプリケーションを動かすケースのため、コンテナプロセスに対して環境変数を注入することになります。それであれば、必要なプロセス以外からのクレデンシャル情報へのアクセスはある程度避けることができそうです。

管理観点

YAMLやTOMLファイルで設定する際、次のように要素に応じて階層的に管理したいというニーズがあると思います。

[database1]
user = "sample_user1"
password = "sample_password1"
host = "database1"
port = 3306
name = "sample1"

[database2]
user = "sample_user2"
password = "sample_password2"
host = "database2"
port = 3306
name = "sample2"

環境変数での注入の場合、単に環境変数に直接書くのであれば階層をもたせることは少し難しそうですが、AWSであればAWS Systems Manager パラメータストアを用いることで階層管理を実現できそうです。

AWS Systems Manager パラメータストア は、設定データ管理と機密管理のための安全な階層型ストレージを提供します。パスワード、データベース文字列、ライセンスコードなどのデータをパラメータ値として保存することができます。

AWS Systems Manager パラメータストア - AWS Systems Manager

概要構成

今回の概要構成は下図のものです。

f:id:khigashigashi:20180828202554p:plain

大まかな構成要素は以下になります。

略語 正式名称 概要
ECS Amazon Elastic Container Service Docker コンテナをサポートする拡張性とパフォーマンスに優れたコンテナオーケストレーションサービス
ECR Amazon Elastic Container Registry 完全マネージド型の Docker コンテナレジストリ
Fargate AWS Fargate ECS/EKS内のテクノロジー、サーバークラスターを管理することなくコンテナを実行できるようになる
IAM AWS Identity and Access Management AWS リソースへのアクセスを安全に制御するためのウェブサービス
Parameter Store AWS Systems Manager Paramter Store 設定データ管理と機密管理のための安全な階層型ストレージ
KMS AWS Key Management Service データの暗号化に使用するキーの容易な作成および管理
ALB Application Load Balancer L4/L7で機能するロードバランサー

具体的な手順

今回のサンプルは下記のgithubレポジトリに公開しています。

github.com

※ 今回のサンプルをそのまま試しに実行する場合は、なにかしらのMySQLサーバを準備する必要があります。サンプルを実行したい場合は、RDSの無料枠などを利用して接続可能なデータベースを用意してください。

手順一覧

  • KMSで暗号化キーの作成
  • Parameterの登録
  • IAMロールの作成
  • IAMポリシーの作成
  • IAMロールにIAMポリシーをアタッチ
  • Containerイメージの作成・プッシュ
  • ECSのタスク定義
  • タスク実行

KMSで暗号化キーの作成

まずは、クレデンシャル情報を暗号化するためのキーをKMSで作成します。作成に当たり、AWS CLIcreate-keyコマンドを実行します。

$ aws kms create-key --description go-ecs-sample --region ap-northeast-1

KEYMETADATA < AWSAccountId > arn:aws:kms:ap-northeast-1:< AWSAccountId >:key/< KeyId > go-ecs-sample True < KeyId > CUSTOMER Enabled ENCRYPT_DECRYPT AWS_KMS

上記コマンドを実行すると、CMK(customer master key)というデータの暗号化に用いるキーが作られます。後続で実行する作業にて、< AWSAccountId >< KeyId >を使っていきます。

Parameterの登録

次に、Parameter Storeにクレデンシャル情報を登録していきます。今回は、RDSへの接続情報を登録していきます。

$ aws ssm put-parameter --name /goecssample/database/sample/master/user --type "String" --value "user" --description "データベースのmasterユーザー名" --region ap-northeast-1
$ aws ssm put-parameter --name /goecssample/database/sample/master/password --type "SecureString" --value "password" --key-id "< KeyId >" --description "データベースのmasterユーザーパスワード" --region ap-northeast-1
$ aws ssm put-parameter --name /goecssample/database/sample/master/host --type "String" --value "host" --description "データベースのmasterホスト名" --region ap-northeast-1
$ aws ssm put-parameter --name /goecssample/database/sample/master/name --type "String" --value "sample" --description "データベースのmasterデータベース名" --region ap-northeast-1
$ aws ssm put-parameter --name /goecssample/database/sample/master/port --type "String" --value "3306" --description "データベースの接続ポート" --region ap-northeast-1

上記のコマンドを実行すると次のようなレスポンスが返ってきます。

1
1
1
1
1

このレスポンス結果は、各パラメータのバージョンを表しています。これは--overwriteオプションを付けて上書きしたりすると、2・3と増えていきます。 また、環境変数を扱う上での階層管理ですが、Parameter Storeを使う場合は、/stage1/stage2/stage3という形で階層を区切ることが出来るので、今回活用しています。

AWS System Manager: パラメータを階層に編成

IAMロールの作成

次に、ECSタスクのタスクロールとなるIAMロールを作成します。タスク用のIAMロールを作成し、タスク定義に当該IAMロールを定義することによって、IAMロールの認証情報にアクセスすることができるようになります。今回、Parameter Store・KMSにアクセス可能なIAMロールを作成して、ECSのタスク定義に定義することを目指します。

docs.aws.amazon.com

まず、IAMロールの定義をjsonで作成します。

{
   "Version": "2012-10-17",
   "Statement": [
     {
       "Sid": "",
       "Effect": "Allow",
       "Principal": {
         "Service": "ecs-tasks.amazonaws.com"
       },
       "Action": "sts:AssumeRole"
     }
   ]
 }

作成したファイルをもとにIAMロールを作成します。

$ aws iam create-role --role-name go-ecs-sample --assume-role-policy-document file://ecs-tasks-trust-policy.json

ROLE arn:aws:iam::< AWSAccountId >:role/go-ecs-sample 2018-08-24T02:10:14Z / HFJKHSYGHOSUHG... go-ecs-sample

IAMポリシーの作成

次に、IAMポリシーを作成します。まずは次のようなjsonファイルを作成します。ここでは、「Parameter Storeのgoecssample/以下の階層のパラメータを取得する」・「今回作成したKMSキーで暗号化したものを複合する」ことを許可しています。

{
   "Version": "2012-10-17",
   "Statement": [
     {
        "Effect": "Allow",
        "Action": [
            "ssm:DescribeParameters"
        ],
        "Resource": "*"
     },
     {
       "Sid": "Stmt1482841904000",
       "Effect": "Allow",
       "Action": [
            "ssm:GetParameters"
       ],
       "Resource": [
            "arn:aws:ssm:ap-northeast-1:< AWSAccountId >:parameter/goecssample/*"
       ]
     },
     {
        "Sid": "Stmt1482841948000",
        "Effect": "Allow",
        "Action": [
            "kms:Decrypt"
        ],
        "Resource": [
            "arn:aws:kms:ap-northeast-1:< AWSAccountId >:key/< KeyId >"
        ]
     }
   ]
 }

作成したファイルをもとにIAMロールを作成します。

$ aws iam create-policy --policy-name go-ecs-sample --policy-document file://go-ecs-secret-access.json

POLICY arn:aws:iam::< AWSAccountId >:policy/go-ecs-sample 0 2018-08-24T02:13:46Z v1 True / 0 HFJKHSYGHOSUHG... go-ecs-sample 2018-08-24T02:13:46Z

IAMロールにIAMポリシーをアタッチ

作成したIAMロールにIAMポリシーをアタッチします。

$ aws iam attach-role-policy --role-name go-ecs-sample --policy-arn "arn:aws:iam::< AWSAccountId >policy/go-ecs-sample"

Dockerイメージの作成・プッシュ

ここまででParameter Storeから必要な情報を取る準備はできたので、次にECSタスクで実行するためのDockerイメージを作成していきます。今回のサンプルは、MySQLサーバに接続可能であればサーバが立ち上がるAPIです。

https://github.com/Khigashiguchi/go-ecs-example/blob/master/main.go

func main() {
    var err error

    // Get configuration
    conf, err := config.NewConfig()
    if err != nil {
        fmt.Fprintf(os.Stderr, "failed to get configuration: %s", err)
        panic(err.Error())
    }

    // Get database Handle
    db, err := NewDB(conf.DB)
    if err != nil {
        fmt.Fprintf(os.Stderr, "failed to get connection with database: %s", err)
        panic(err.Error())
    }

    // Router
    r := mux.NewRouter()
    h := Handler{DB: db}
    r.Methods("GET").Path("/posts").HandlerFunc(h.GetPostsHandler)
    r.Methods("GET").Path("/.healthcheck").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
    })

    // Serve HTTP service
    fmt.Fprint(os.Stdout, ">> Start to listen http server post :80\n")
    if err = http.ListenAndServe(":80", r); err != nil {
        fmt.Fprintf(os.Stderr, "failed to start http server: %s", err)
        panic(err.Error())
    }
}

上記の簡単なAPIを動かすために次のようなDockerfileを書きます。ENTRYPOINTからaws-cliを叩くためにawscliをインストールしています。

https://github.com/Khigashiguchi/go-ecs-example/blob/master/Dockerfile

FROM golang:1.10-alpine3.7

WORKDIR /go/src/github.com/Khigashiguchi/go-ecs-example/
COPY . /go/src/github.com/Khigashiguchi/go-ecs-example/

RUN apk add --no-cache ca-certificates \
    dpkg \
    gcc \
    git \
    musl-dev \
    openssh \
    bash \
    curl \
    python

# Install the AWS CLI
# https://aws.amazon.com/jp/blogs/news/managing-secrets-for-amazon-ecs-applications-using-parameter-store-and-iam-roles-for-tasks/
RUN curl -O https://bootstrap.pypa.io/get-pip.py
RUN python get-pip.py
RUN pip install awscli

RUN go get -u github.com/golang/dep/cmd/dep

RUN dep ensure

RUN go build -v -o server

EXPOSE 80
ENTRYPOINT ["./docker-entrypoint.sh"]
CMD ["./server"]

ENTRYPOINTで指定しているdocker-entrypoint.shは次のようなものです。ローカル環境では叩きに行かずAWS ECS上でのデプロイ時のみParameter Storeにアクセスしたいため、ECS上でのデプロイ時のみ事前付与する環境変数PARAMETER_STORE_PREFIXを用意しています。

https://github.com/Khigashiguchi/go-ecs-example/blob/a2cba61c6c03b6eb22ae449346c51a65d22f5617/docker-entrypoint.sh

#!/usr/bin/env bash

set -e

PARAMETER_STORE_PREFIX=${PARAMETER_STORE_PREFIX:-}
if [ -n "$PARAMETER_STORE_PREFIX" ]; then
    export DB_USER=$(aws ssm get-parameters --name /${PARAMETER_STORE_PREFIX}/database/sample/master/user --query "Parameters[0].Value" --region ap-northeast-1 --output text)
    export DB_PASSWORD=$(aws ssm get-parameters --name /${PARAMETER_STORE_PREFIX}/database/sample/master/password --with-decryption --query "Parameters[0].Value" --region ap-northeast-1 --output text)
    export DB_HOST=$(aws ssm get-parameters --name /${PARAMETER_STORE_PREFIX}/database/sample/master/host --query "Parameters[0].Value" --region ap-northeast-1 --output text)
    export DB_NAME=$(aws ssm get-parameters --name /${PARAMETER_STORE_PREFIX}/database/sample/master/name --query "Parameters[0].Value" --region ap-northeast-1 --output text)
    export DB_PORT=$(aws ssm get-parameters --name /${PARAMETER_STORE_PREFIX}/database/sample/master/port --query "Parameters[0].Value" --region ap-northeast-1 --output text)
fi

exec "$@"

これを、ECRのレポジトリにイメージプッシュします。レポジトリの作成手順は下記をご参照ください。

docs.aws.amazon.com

$ $(aws ecr get-login --no-include-email --region ap-northeast-1)
$ docker build -t go-ecs-sample .
$ docker tag go-ecs-sample:latest < AWSAccountId >.dkr.ecr.ap-northeast-1.amazonaws.com/go-ecs-sample:latest
$ docker push < AWSAccountId >.dkr.ecr.ap-northeast-1.amazonaws.com/go-ecs-sample:latest

ECSのタスク定義

ECSのタスク定義を作成します。タスク定義の詳細の説明については、下記の公式ドキュメントをご参照ください。

docs.aws.amazon.com

要点としては、下記2点です。

    1. タスクロールに今回作成したIAMロール`go-ecs-exampleを指定してください。

f:id:khigashigashi:20180828213458p:plain

なお、タスク実行ロールはデフォルトのecsTaskExecutionRoleでOKです。

f:id:khigashigashi:20180828213439p:plain

そして、コンテナ定義で設定する環境変数に次の内容をセットしてください。

環境変数
PARAMETER_STORE_PREFIX goecssample

タスク実行

タスクを登録するとタスクが実行されます。

f:id:khigashigashi:20180828213958p:plain

前回のステータスRunningになっていれば、OKです。

参考資料

aws.amazon.com

qiita.com

techblog.housmart.co.jp

quipper.hatenablog.com

Goのテストに関する"もやもや"がgolang.tokyo#17でとても解消された | golang.tokyo#17参加レポート

f:id:khigashigashi:20180822120705j:plain
golang.tokyo

2018年8月21日(火)に、freee株式会社さんのイベント会場で、golang.tokyo#17が開催されました。今回のテーマは、「今あらためてテストの話」ということだったのですが、普段テストを書いていてもやもやしていた部分が解消されるとてもタメになる内容ばかりでした。 本参加レポートでは、会場の雰囲気から内容までをレポートいたします。

golang.tokyoとは

golangtokyo.connpass.com

プログラミング言語のGoの導入企業のメンバーが集まり、Goの普及を推進するコミュニティです トークイベント、ハンズオン、etcのイベントを開催していく予定です!

すでに、togetterをまとめていただいているので、当日の様子はこちらで見ることができます。 togetter.com

会場の雰囲気

今回の会場は、freee株式会社さんのイベントスペースで行われました。

f:id:khigashigashi:20180822120658j:plain
golang.tokyo会場

f:id:khigashigashi:20180822120701j:plain
golang.tokyo会場風景

f:id:khigashigashi:20180822121047j:plain
freee株式会社

発表内容

Tour of testing by budougumi0617

@budougumi0617さんによる発表です。

前段に、freeeさんの会社について紹介されていて、Railsがメインで動いているところに対して、マイクロサービス化を進めていて、実際4機能ほどGoで書かれたマイクロサービスが動いているようです。

今回の発表では、主に「go testコマンド・testing.TについてGo1.10までの復習」・「テストのベタープラクティス」についてはなされていました。

体系的にまとめてくださっているので、何回でも見返したい神資料でした。

個人的に「なるほど」となったのは、Table Driven Testingでループを回す際、testsで回してttで受け取るという点でした。 標準パッケージのテストの書き方が基本的にこうなっているとのことで、casescでやっていた私は返ってすぐ書き換えました。

非公開な機能を使ったテスト by tenntenn

資料URL:https://docs.google.com/presentation/d/1LLEHlg2IEecaoXnUpKOkMSE1hk9lAWVNJz5VMwmbqm8/edit

Gopher道場で講師をされている、@tenntennさんの発表でした。

テストはテスト対象と別のパッケージにする」事に関する標準パッケージの状況・利点・export_test.goを利用したテスト手法についてご紹介されていました。 発表の際、デモをおこなわれていたのですが、デモで使用されていたコードは以下の記事にて公開されているようです。

tech.mercari.com

今回紹介されている手法は、標準パッケージでも利用されているようなので、標準パッケージのソースを読むのが一番参考になるとのことでした。

また、質疑応答にて、「非公開な機能が多いと大変な書き方では」という意見が上がったのですが、その場合は、そもそも「非公開が多いのはよくない設計なのでは」という見直した上で、internalパッケージにするといった検討が必要になることもあるとのことでした。

LT1 外部環境への依存をテストする by duck8823

www.slideshare.net

@duck8823さんより、CIサーバーを作った際に行った、外部環境への依存をテストする方法についての発表でした。

今回発表されているCIサーバーgithub上に公開していただいています。

github.com

LT2 Developer-friendly なテストを考える by izumin5210

@izumin5210さんよる発表です。

「Developer-friendly なテスト」を「落ちたときに直しやすいテスト」と定義した上で、どのようにテストを書いていくかという点について、Table Driven Testingやgoogle/go-cmpなどを使うやり方をご紹介されていました。

LT3 止めたいのに止められないテストの話 by knsh14

資料URL: https://talks.godoc.org/github.com/knsh14/go-slides/lt-go-test-failfast.slide#1

@knsh14さんによる発表です。

go test -failtestを実際に試した際に意図しない挙動になったことをきっかけに、内部挙動を調査・実験していった内容でした。実際に、go test -failtestはちゃんと動くのかという結論については資料でぜひご確認ください。

まとめ

今あらためてテストの話」というテーマで開催された、golang.tokyo#17でしたが、普段テストを書いていて「これであってるのか???」ともやもやしていた点を話していただいてとても素晴らしい会でした。

golang.tokyoの運営スタッフの皆さん・会場を提供していただいたfreeeの皆さんありがとうございました!