nazolabo

Webエンジニアのnazoさんのブログです。お仕事募集中です。 https://nazo.dev/

クラウドインフラにおけるResourceとDeployment

何の話

クラウドインフラ(AWSGCPなど、その中で取り扱うもの)で、ResourceとDeploymentという境界を分けるとうまく整理できるのではないかという話です。

※この話は私が勝手に言っている内容です。用語定義や概念など一般的なものでないので、あくまで参考として読んでいただければと思います。

前提

現代のアプリケーションは12Factor Appに基づき、Buildフェーズで作られたビルドに環境変数で設定を当てることによってデプロイが完了します。RAILS_ENVのような設定は使いません。

またCIでビルドされたイメージには一意のタグが付き(いわゆるlatest運用ではない)、それに環境変数を当てることで一意のリリースが作成されます。12Factor Appの「V. ビルド、リリース、実行」も参考にしてください。

Resource

一般的なクラウドリソースをResourceと呼びます(ダジャレを言いたいわけではないです)。

Resourceは一度作ると「そのResourceが持っている機能で」操作を行うことがありますが、Resourceそのものを作成した時と同じ方法で操作することはほぼありません。

Terraformは現代においてベストなIaCツールの1つですが、その理由に「そうなるべきである状態を定義し、インフラ側をその状態になるようにする」という構造で作られているからです。

「あるべき状態を定義してそれを実行する」という店ではAnsibleもそうでしたが、Ansibleは書き方が手続き型で、かつ何でも書けてしまうという難点がありました。しかしTerraformは書き方を大幅に制限することで、「インフラのあるべき状態」以外のことはほぼ書けないようになっています。

Deployment

ビルドしたリソースを配置する作業に付随するものはDeploymentです。

Resourceは一度作ると「その作業プロトコルで」それ以上の操作をすることは基本的にありませんが、Deploymentは「同一の作業プロトコルで」初期配置やビルドの更新などを行います。

例えばk8sのコントロールプレーン(AWSならEKS)はResourceですが、その上で行うk8sの操作は全てDeploymentと分類できます。(k8s自体のDeploymentとは別の話です。DaemonSetだろうとLoadBalancerだろうとここでは全てDeploymentです)

ちょっと話が逸れますが、AWS RDSそのものはResourceで、その中に入るDBのデータはDeployment、というようなイメージを、インフラ全体とアプリケーションに対して定義しているような感じだと思っています。

なぜ分けるのか

例えばAWS ECSのタスク定義には通常最新のイメージのタグを記述しますが(これはk8sでも同様ですが)、最新のイメージのタグは通常CIで決定されますので、アプリケーションのビルドCIと同居している必要があります。タスク定義のライフサイクルがCIと違う場所に存在する、例えばクラウドリソースと同じライフサイクルで存在すると、CIに入れてしまうとアプリケーションの更新とクラウドリソースの更新が混ざる可能性がありますし、CIに入れないと最新のタグを取得できないので最新のイメージで適切に更新する方法がありません。(latestタグ運用はダメ絶対)

つまり、Resourceというのは「変化しないもの」であり(厳密には自動メンテナンスで勝手にバージョンを上げられて…みたいなこともありますが)、Deploymentは「変化するもの」ということです。

私はECSのサービスやタスク定義をTerraformで他のResourceと同一ライフサイクルで管理したことがありましたが、上記問題により辛い結果になっていました(その時はlatest的なタグをCIから同時にpushすることで、Terraformから更新する時だけは仕方なくそちらを参照するという苦肉の策で回避していました)。TerraformでECSタスク定義やk8sを管理すること自体は否定しませんが(それが良いかどうかは別として)、Resourceと同一のライフサイクルにするのは無理があるのではないかと考えています。

k8sYAMLは一見「あるべき状態を書く」ようになっていますが、それは通信プロトコル的な部分の話で、現実にはCIで毎回違うイメージのタグに書き換えてデプロイすることになります。このYAMLの部分は下記参考記事のInfrastructure as Dataであり(この呼び方は議論の余地があると思いますが、概念の分類としては良いと思います)、それを実運用するには通常その上にCodeが必要になります。

deeeet.com

個人的には、Deploymentというのはインフラというより「アプリケーションという名のデータ」というほうが近く、そもそもこれはInfrastructureなのか?と思うところがありますが、これがk8sになるとALB Ingress Controllerやらexternal-dnsやらでクラウドリソースそのものも制御できてしまうので、このあたりがk8sをより複雑にさせているのではないかと思うところです。

まとめ

用語は正直何でもいいし英語圏の人たちにちゃんと決めてほしいのですが、ともかく「デプロイするコンテナ」というのは通常のクラウドリソースとは別物なのではないか、ということが言いたかったことです。

Deploymentは「あるべき状態」が日々変化するため、そういう目的で管理するのが難しいですが、しかし統一的な「あるべき状態」を維持できるツールにしておかないと管理できていないサーバー(「構成ドリフト」とか「スノーフレークサーバー」とかそういうやつ)が生まれます。Resourceとはライフサイクルを分けつつ、コードと実態が乖離しない、そもそも乖離できないようにする構成を考えましょう。

アタッチされたサービスとTerraformのModuleの分け方

何の話

TerraformのModuleの分け方はマイクロサービス的な単位で分けると良いという話です。TerraformだけではなくてIaC全般に言える話だと思います。

アタッチされたサービス

Twelve-Factor AppのIV. バックエンドサービスでは、バックエンドサービス(DBやキャッシュなど)はアタッチされたサービスとして扱う、としております。

f:id:nazone:20200502144124p:plain

設定は環境変数で接続先を具体的に記入し、アプリケーションコード(ビルド)の差し替えなしにバックエンドサービスを切り替えれるようにすべきとしております。

これにより様々なメリットが生まれますが、ここでは特に責任分界点がこの接続先によって明確になるということがあります。

f:id:nazone:20200502144709p:plain

マイクロサービスの設計では独立性が重要になりますので、責任分界点が明確になっているということは独立性が高いものを組み合わせて安全に利用できている状態と言えると思います。

TerraformによるModule化の境界

Terraformに限らずIaCではインフラの単位をコードとして再利用しようとすると失敗しやすく、例えば「フロントエンド用のnginx」と「画像変換サーバー用nginx」を「同じnginxだから」と言って「同じmodule化」すると確実に失敗します。

今の例は見ただけで失敗だとわかると思いますが、ようは複雑性の意味が全然違うものを共通にしようとすると、その複雑性を隠蔽するためにより複雑な汎用化をしないといけなくて本末転倒になるという話です。

TerraformにおけるModule化は、「共通のものを1つにまとめる」という用途より、「名前空間の分離」を目的として設計するほうが上手くいくと考えております。

例えばTerraformでAWSリソースをModule化する場合、以下のようにするのが良いです。

f:id:nazone:20200502150041p:plain

上記の「アタッチされたサービス」の図とほぼ同じなのがわかるかと思います。責任分界点が事前に明確になっているので、その単位でModule化することによって、Module内でお互いのことを意識することがほとんどなくなります。Module間で必要な情報(エンドポイントなど)はvariablesとoutputで明確に書く必要がありますので、どの情報が外で使われるかも明確になります。

IAM(Role)やSG(セキュリティグループ)もModule毎に個別で作ることで、変に再利用されることがなく、最小の権限を個別に与えることができます。別環境の同リソース(staging/productionなど)や似たような権限のModuleでも必ず個別に作りましょう。

Moduleは汎用的に使えないの?

もちろんそんなことはありません。責任分界点がしっかりしているModuleは汎用的になります。一般的なWebサービスの構成はそんなにどれも違いがないのでModule単位で別プロジェクトに持ち出しなども簡単にできるようになると思います。以前紹介した terraform-aws-modules などの設計も参考にしてください。

参考

memo.yuuk.io

Unity で gRPC を試す( HelloworldUnity )

概要

Unity で gRPC を試すには、以前はいろいろ準備する必要があったようですが、現在は https://github.com/grpc/grpc/tree/master/examples/csharp/HelloworldUnity にサンプルが丸ごと入っていますので、これを動かすところまで試します。

手順

上記を UnityEditor で開きます。

README.md に書いてある通り、別途 grpc_csharp_unity.zip が必要です。指定場所からダウンロードして展開して出てきた Plugins ディレクトリをプロジェクトに突っ込みます。

エラーが出る

Error: Could not load signature of Google.Protobuf.ByteString:get_Span due to: Could not load file or assembly 'System.Memory, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' or one of its dependencies. assembly:System.Memory, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 type:<unknown type> member:(null) signature:<none>

こんな感じのエラーが出るのですが、grpc_unity_package.2.26.0-dev までは動作するが、それ以上のバージョンではエラーになるようです。いずれ解決する問題かどうかはよくわかりません。ちなみに grpc_unity_package.2.26.0-dev はここからダウンロードできます

動かす

f:id:nazone:20200505183848p:plain

動かすとこんな画面になります。

ちなみに Unity 側単体でサーバーとクライアントが両方起動しているので、通信はしているのですがよくわからないと思います。

ボタンを押すと表示が変わります。

f:id:nazone:20200505183906p:plain

サーバーを別で動かす

HelloWorldTest.cs に、Server server を new している一帯がありますので、そこを削除なりコメントアウトなりします。

同等のサーバーは https://github.com/grpc/grpc/tree/master/examples/csharp/Helloworld/GreeterServer にありますので、代わりにこちらをビルドして起動してから Unity 側を起動すると、いい感じで通信するようになります。

Webエンジニアとしてフリーランスしています

これまでのあらすじ(&お前誰よ)

UUUM株式会社を2019年10月に退職し、のんびりとした生活をしていましたが、本格的に働くことにしました。

能力や経歴などについてはポートフォリオを作りましたので、そちらをご覧頂ければと思います。

なぜフリーランス

働き方を自分で制御したいというのと、会社員という立場から一度離れていろいろな現場を見てみたい、そして私の力でそれを改善できるのか挑戦してみたい、という点から、しばらくフリーランスをすることを選びました。

特に最近は一人で全て解決することを求められることが多く、他の会社の視点ではどのようになっているのか、どのような他の良い方法があるのかというのをもっと知りたくなり、様々な現場を見てみたいという気持ちが強いです。

前にも少しやっていたのですが、当時の居住地や知名度では正直難しい(私の営業力不足も大きいですが…)状況で断念していました。

全く就職しないの?

そのうちすると思います。フリーランスという形態は自分には向いていないと思っておりますので、よほど軌道に乗って収入に困らない状態が続くとかでなければ就職するのではないかと思います。

その場合にしても、フリーランス中にお試しで仕事をしてマッチしたら入社という形態を取らせていただきたいと考えておりますので、興味がある会社様は先に声をかけていただいておくと良いと思います。

仕事募集してるの?

ありがたいことに今(2020年3月現在)は埋まっているので受けられないのですが、フリーランスなので状況はすぐ変わると思いますので、お仕事の相談をしたい方は先にご連絡頂ければと思います。本記事公開以降も状況が変わっているかもしれませんので、詳しくはお問い合わせ頂ければと思います。

原則としてリモートが可能な現場のみ受け付けております。たまには出社してほしいとかという場合でもお受けできますが、そのあたりは直接ご相談頂ければと思います。また最大でも週4まで、通常週2程度の稼働で受けております。

Terraform AWS modulesでざっくりAWSインフラを作る

https://github.com/terraform-aws-modules には、TerraformですぐにAWSリソースを立ち上げるためのModuleがいくつか存在します。

VPCの作成

terraform-aws-vpcでは、主に以下のリソースを作ってくれます。オプションでNATとかも用意してくれます。

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"

  name = "test"
  cidr = "10.0.0.0/16"

  azs             = ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

  enable_nat_gateway = false
  enable_vpn_gateway = false
}

ALBの作成

terraform-aws-albでは、主に以下のリソースを作ってくれます。

Security Group は作ってくれないので、自前で用意しましょう。

resource "aws_security_group" "alb" {
  name        = "alb"
  description = "alb"
  vpc_id      = module.vpc.vpc_id
}

module "alb" {
  source  = "terraform-aws-modules/alb/aws"
  version = "~> 5.0"

  name = "test"

  load_balancer_type = "application"

  vpc_id          = module.vpc.vpc_id
  subnets         = module.vpc.public_subnets
  security_groups = [aws_security_group.alb.id]

  target_groups = [
    {
      name             = "test"
      backend_protocol = "HTTP"
      backend_port     = 80
      target_type      = "ip"
      deregistration_delay = 10
    }
  ]

  http_tcp_listeners = [
    {
      port               = 80
      protocol           = "HTTP"
      target_group_index = 0
    }
  ]
}

ACM

terraform-aws-acmでは、ACM証明書を一発で作ってくれます。これは定型フォーマットなので、そのまま利用して問題ないでしょう。(手元にRoute53管理のドメインがなかったので試してはいません)

module "acm" {
  source  = "terraform-aws-modules/acm/aws"
  version = "~> v2.0"

  domain_name  = "my-domain.com"
  zone_id      = "Z2ES7B9AZ6SHAE"

  subject_alternative_names = [
    "*.my-domain.com",
    "app.sub.my-domain.com",
  ]

  tags = {
    Name = "my-domain.com"
  }
}

SNSのSlack通知

terraform-aws-notify-slackを使うと、SNSトピックをSlackに通知させるやつも簡単にTerraformで管理することが可能です。おそらくこれが一番使うのではないでしょうか。

使い方も簡単で、よくあるCloudWatch AlarmをSlackに通知するのは以下の書き方で実装できます。(手元に通知するものがなかったので試していません)

  
provider "aws" {
  region = "eu-west-1"
}

resource "aws_kms_key" "this" {
  description = "KMS key for notify-slack test"
}

# Encrypt the URL, storing encryption here will show it in logs and in tfstate
# https://www.terraform.io/docs/state/sensitive-data.html
resource "aws_kms_ciphertext" "slack_url" {
  plaintext = "https://hooks.slack.com/services/AAA/BBB/CCC"
  key_id    = aws_kms_key.this.arn
}

module "notify_slack" {
  source = "../../"

  sns_topic_name = "slack-topic"

  slack_webhook_url = aws_kms_ciphertext.slack_url.ciphertext_blob
  slack_channel     = "aws-notification"
  slack_username    = "reporter"

  kms_key_arn = aws_kms_key.this.arn

  lambda_description = "Lambda function which sends notifications to Slack"
  log_events         = true

  tags = {
    Name = "cloudwatch-alerts-to-slack"
  }
}

resource "aws_cloudwatch_metric_alarm" "LambdaDuration" {
  alarm_name          = "NotifySlackDuration"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = "1"
  metric_name         = "Duration"
  namespace           = "AWS/Lambda"
  period              = "60"
  statistic           = "Average"
  threshold           = "5000"
  alarm_description   = "Duration of notifying slack exceeds threshold"

  alarm_actions = [module.notify_slack.this_slack_topic_arn]

  dimensions = {
    FunctionName = module.notify_slack.notify_slack_lambda_function_name
  }
}

https://github.com/terraform-aws-modules/terraform-aws-notify-slack/blob/master/examples/cloudwatch-alerts-to-slack/main.tf

まとめ

公式とはいえ外部にあるmoduleだと、例えば最新の機能を入れたい場合にmoduleの反映を待たないといけないですし、そういう理由以外でもカスタマイズすることが難しく、また、moduleが勝手にアップデートされることが嬉しくないことも多いため、実際に使うのであればローカルにmoduleまるごどcloneして利用するのがいいのではないでしょうか。

SNSのSlack通知やACMに関してはほぼそのまま使っても問題ないと思います。

今はVPC一通りとALBがあればECS on Fargateでクラスタを作ればサービスを上げられるので(DBとかは別にして)、Terraformで管理したいけど書き方が難しいという場合は使ってみるのもありなのではないでしょうか(そんなに難しくないですが…)。

SSH先のdockerをローカルからシームレスに使用する(SSHでDOCKER_HOSTを指定する)

何がしたいの?

例えば「MacBook Air で開発しているけどスペックが低いので Docker で動かすものはデスクトップPCで動かしたい」というようなケースに使います。

docker をリモートで動かす

まずリモートの Docker を用意します。私は Windows なので Hyper-V で作りました。Docker のバージョンはリモートもローカルも最新が良いです。最低でも 18.09 以上が必要かと思われます。

対象の環境に SSH で入れるようにしておいて、SSH で入るユーザーが sudo なしで docker コマンドを叩ける必要があります。 sudo usermod -aG docker myuser のようにして SSH 対象ユーザーを docker グループに所属させましょう。

docker コマンド内で SSH 鍵のプロンプトは出せないので、事前に ssh-add しておきましょう。

ここまでで、設定が問題なければ以下の方法で起動すると思います。

docker -H ssh://user@remote-host run -d -P nginx

-H の部分は、環境変数 DOCKER_HOST で指定が可能です。

docker-compose 対応

Docker for Mac 標準の docker-compose ではうまく動かなかったので(多分 このあたりの都合 )、docker-compose は pip で入れました。pip で入れただけでも動かない場合は pip install cryptography==2.4.2 が必要かもしれません。

また、 docker-compose は .ssh/config を読んでくれないっぽいので(1.25.0 時点)、DOCKER_HOST はそれを考慮して書いてください。

ボリュームマウントは SSH 先のホストに対して行われます。マウント先は SSH 先のホストのディレクトリ構成に合わせて記述してください。ローカルと同期したい場合は別途自前で同期する必要があると思います。

ユースケース

ローカルとボリュームマウントが基本的にできないので、アプリケーションコードを Docker コンテナ内で実行するようなケースには向いていません。特殊な構成のアプリケーションで実行に Docker がどうしても必要という場合は、この方法は避けたほうが良いです。

ローカルで編集しないもの、DB やキャッシュサーバーのようなものだけ Docker で起動し、アプリケーションはローカルで起動する、といったケースにはすぐに使用できると思います。この場合も設定ファイルを送り込むことができないので、設定ファイルが必要なもの(Elasticsearch など)は設定ファイルを入れるための Dockerfile を書いて、それ経由で起動するのが良いかと思われます。

現代の一般的な構成の Web アプリケーションでは、Docker でアプリケーションそのものを実行するメリットはあまりないので、この構成は十分実用的かと思われます。

そもそもSSH先で開発すればいいのでは?VSCodeのRemote-SSHもあるし…

あっはい…

一応ローカルに開発中のファイルを置いておけるので、外出先で開発したくなった場合にそれをそのまま使えるというメリットはありますが、まあ適当に作業中ブランチをpush/pullすればいいだけですね…

2019年トップツイート振り返り

Twitter Analyticsから各月のトップツイートについて振り返ってみます。

1月

ありません

2月

今でもこういう感想だし、下手に並列化するより直列でそのまま流したほうが(個別のジョブの時間が長すぎる場合以外は)速いという認識なのですが、皆様どうしてるのでしょうか。

3月

EntityやらValueObjectとかってのはDDDの表現手法の一つであって、本質としてDDDが目指してるものってそこじゃないでしょ?みたいな話でした。

4月

自分もそれなりにTerraform書いている量では多い自信があるのですが、こうやって具体例がまとめられている書籍は今までなかったのでとても良い内容でした。

現在はさらに内容を充実させた 実践Terraform AWSにおけるシステム設計とベストプラクティス が出ているようなので、そちらを読むのが良いのではないかと思われます。(私はまだ買ってないです…)

実践Terraform AWSにおけるシステム設計とベストプラクティス (技術の泉シリーズ(NextPublishing))

実践Terraform AWSにおけるシステム設計とベストプラクティス (技術の泉シリーズ(NextPublishing))

5月

通信レイヤーを使ってサービスを分離するのは規模が大きくないと難しいですが、モノリシックなプロダクトでも内部構造は分離できる構造になっていると変更に強いしテストも書きやすいしという話です。マイクロサービスが目指したいものが何なのかを考えて導入しましょう。

6月

リモートワーク論についてはいろいろあるのですが、自然にリモートワークをするにはオフィスを提供しても提供しなくても何も変わらないというような環境がベストではないかと思います。そのためにはオンラインにリソースを集中させる必要がありますし、顔を合わせないとできないことを最小限にする必要もあります。

7月

Terraform人気ですね。ACMDNS検証周りは内部でオーダーが勝手に変わるらしく突然差分が発生するというやつです。最近は諦めてここだけ手動でやっています。管理するメリットないし…。

8月

2019後半は本当に何もしていないので、2019年のアウトプットのほとんどはここに凝縮されています。12月の登壇内容と併せてご覧いただければと思います。

9月

リモートワーク大好きマンです。身も蓋もない話なのですが、できる人はできるしできない人はできないみたいな結論に行き着きます。そして多くの人はどちらかといえば「できない」ほうです。主語が大きいですが根拠はあるのでどこかで書けたらと思います。

10月

大きめの企業で「誰々さんを新たな役員に任命!」みたいなのって、大きく会社の方向性を変えたいという意思でもあると思うので、当然決断するのは大変だし、失敗することも多いと思うし、それでも決断するのはすごいなーという話です。私には無縁の内容ですが…。

なお、このツイートの次はこんな内容です。

11月

元ネタがあります

12月

これについては年が明けたら詳しく書きます。それではよいお年を。