長生村本郷Engineers'Blog

千葉県長生村本郷育ちのエンジニアが書いているブログ

Ansible FAILED! => {"msg": "to use the 'ssh' connection type with passwords, you must install the ssh pass program"} on MacOS

MacOS で Ansible を利用した所、掲題のようなエラーが発生しました。

その際の対策です。

brew install http://git.io/sshpass.rb

インストール完了まで少々時間かかりました。

以上 参考になれば幸いです。

参照

everythingshouldbevirtual.com

puppeteer で radio ボタンチェック

f:id:kenzo0107:20190906230848p:plain
puppeteer radio button

概要

puppeteer というスクレイピングツールで radio ボタンをチェックする為の備忘録です。

例題

以下のような radio ボタングループがあるとします。

<input type="radio" name="maker" value="1"> クリスタル映像
<input type="radio" name="maker" value="2"> ポセイドン企画

答え

クリスタル映像 を選びたい場合は、以下のようにします。

page.evaluate でブラウザ内での操作結果を返すようにします。
ブラウザ内なので、 document.querySelector が使えます。
そこで、 checked = true しています。

const selectedRadioSelector = `input[type="radio"][value="1"]`
await page.evaluate(
  s => (document.querySelector(s).checked = true),
  selectedRadioSelector
)

失敗例

click 処理は軒並み失敗しました。

page.WaitFor すると成功する、という記事を見ましたが、自身の環境 puppeteer (version=1.19.0) では、失敗してしまいました。

  • page.click
const selectedRadioSelector = `input[type="radio"][value="1"]`
page.click(selectedRadioSelector)
  • radio 要素を捕まえて、click
r = page.$(selectedRadioSelector)
r.click()
  • document.querySelector().click()
const selectedRadioSelector = `input[type="radio"][value="1"]`
await page.evaluate(
  s => (document.querySelector(s).click()),
  selectedRadioSelector
)

応用

以下のような radio ボタングループがあるとします。
上のと比べると label タグが追加されてます。

<input type="radio" id="group1" name="maker" value="1"> <label for="group1">クリスタル映像</label>
<input type="radio" id="group2" name="maker" value="2"> <label for="group1">ポセイドン企画</label>

このラベルを正規表現でマッチする方をチェックしてみます。

  • "映像" という文字を含む方をチェックする
const regex = "映像"
const regexpLabel = new RegExp(regex, 'g')

const r = await page.$$('input[type="radio"]')

label: for (const i in r) {
  // radio ボタンの id 要素
  const id = await (await r[i].getProperty('id')).jsonValue()
  // radio ボタンの value 要素
  const value = await (await r[i].getProperty('value')).jsonValue()

  // ラベル textContent 取得 ("クリスタル映像", "ポセイドン企画" を取得)
  const label = await page.$(`label[for="${id}"]`)
  const labelContent = await (await label.getProperty(
    'textContent'
  )).jsonValue()


  // ラベルの textContent が "映像" を含む場合、 true
  if (labelContent.match(regexpLabel)) {
    const selectedRadioSelector = `input[type="radio"][value="${value}"]`
    await page.evaluate(
      s => (document.querySelector(s).checked = true),
      selectedRadioSelector
    )
    // radio ボタンにチェック入れたので処理終了
    break label
  }
}

まとめ

非常につまづきやすい点だったので備忘録として残しました。

参考になれば幸いです。

WEB+DB PRESS Vol.109

WEB+DB PRESS Vol.109

  • 作者: 佐藤歩,加藤賢一,原一成,加藤圭佑,大塚健司,磯部有司,村田賢太,末永恭正,久保田祐史,吉川竜太,牧大輔,ytnobody(わいとん),前田雅央,浜田真成,竹馬光太郎,池田拓司,はまちや2,竹原,原田裕介,西立野翔磨,田中孝明
  • 出版社/メーカー: 技術評論社
  • 発売日: 2019/02/23
  • メディア: 単行本
  • この商品を含むブログを見る

puppeteer on Docker

概要

備忘録です。

Puppeteer をローカル環境を汚さず、 Docker 環境上で実行すべくまとめました。

サンプルスクリプトexample.comスクリーンショットを取得する、というシンプルなものです。

github.com

WEB+DB PRESS Vol.109

WEB+DB PRESS Vol.109

  • 作者: 佐藤歩,加藤賢一,原一成,加藤圭佑,大塚健司,磯部有司,村田賢太,末永恭正,久保田祐史,吉川竜太,牧大輔,ytnobody(わいとん),前田雅央,浜田真成,竹馬光太郎,池田拓司,はまちや2,竹原,原田裕介,西立野翔磨,田中孝明
  • 出版社/メーカー: 技術評論社
  • 発売日: 2019/02/23
  • メディア: 単行本
  • この商品を含むブログを見る

EC2 Instance Connect API で ssh ログインできるインタラクティブ cli tool "omssh" を作ってみました。

概要

oreno-mssh、またの名を omssh という AWS EC2 Instance Connect API を利用した ssh ログインツールを作成しました。

View post on imgur.com
imgur.com

作ろうと思った経緯

以前 EC2 Instance Connect API の登場により、EC2 Instance ID 指定で ssh ログインできる様になりました。

これにより、ssh ログイン接続するメンバーに秘密鍵を渡す、公開鍵を登録する等の作業が不要となりました。

kenzo0107.hatenablog.com

mssh を使用した場合、以下の様なコマンドで ssh ログインできます。

// Amazon Linux への ssh 
mssh <EC2 Instance ID> --profile <profile>

// Ubuntu
mssh ubuntu@<EC2 Instance ID> --profile <profile>

mssh を利用するには <EC2 Instance ID> 情報が必要で、毎回 Instance ID を調べる手間がありました。

もちろん踏み台サーバであれば、そうそう再起動され Instance ID が変更されることはないのでメモっておけば良いのでしょうが、 数が多くなると、管理が大変です。

その手間を fuzzyfinderインタラクティブに解決しようと思いました。

使用方法

README.md にもありますが、以下ステップでインストールできます。

$ git clone https://github.com/kenzo0107/omssh
$ cd omssh
$ make build && make install

今後

現在、EC2 Instance Connect を利用した運用に切り替えきれないところがあります。

理由は、EC2 インスタンスを Public Subnet に配置していないと EC2 Instance Connect API が利用できない為、踏み台までは EC2 Instance Connect API を利用し ssh ログインできたけど、その先は、秘密鍵が必要になる為です。

f:id:kenzo0107:20190804180237p:plain

おそらく近々 Private Subnet でも EC2 Instance Connect が利用できる様になるのでは?と期待しています。

そうなれば、踏み台にも omssh を置いて、鍵を意識せず、 IAM の権限だけで、 ssh 権限を管理できる様な世界が実現できます。

AWS サポートに願いを伝えておきます♪

改訂2版 みんなのGo言語

改訂2版 みんなのGo言語

EC2 Instance Connect で AWS EC2 への ssh 管理を IAM User or Group で簡単に♪

f:id:kenzo0107:20190628154100p:plain

概要

2019-06-28 に EC2 Instance Conncet が発表されました!

これによって、セキュリティグループと IAM 権限で ssh アクセス許可が可能になります。

例えば、
会社の IP からのみ、特定の IAM User Group に所属している IAM User に ssh アクセス権限を付与、
別のプロジェクトへ異動した、退職した場合は、その IAM User Group から削除で ssh アクセス権限を剥奪できます。

試験環境

macOS 10.14.3 で試しました。

事前準備

$ pip install -U awscli

$ aws s3api get-object --bucket ec2-instance-connect --key cli/ec2instanceconnectcli-latest.tar.gz ec2instanceconnectcli-latest.tar.gz

$ sudo pip install ec2instanceconnectcli-latest.tar.gz

発行した IAM User のパーミッション権限に以下を追加

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "EC2InstanceConnect",
            "Action": [
                "ec2:DescribeInstances",
                "ec2-instance-connect:SendSSHPublicKey"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}

この辺りは terraform 管理案件ですね。

EC2 Instance Conncect 対応 OS

  • Ubuntu>=16.04
  • AmazonLinux2>=2.0.20190618

ssh ログインする EC2側の設定

Ubuntu>=16.04

ec2-instance-connect をインストールしておく必要があります。

$ sudo apt-get update && sudo apt-get install ec2-instance-connect
$ dpkg -l | grep ec2-instance-connect

ii  ec2-instance-connect           1.1.9-0ubuntu3~18.04.1            all          Configures ssh daemon to accept EC2 Instance Connect ssh keys

AmazonLinux2>=2.0.20190618

ec2-instance-connect は設定済みです。

セキュリティグループ

ssh ログイン先となる EC2 インスタンスのセキュリティグループはアクセス元から ssh (22 port) を開けておく必要があります。

ssh ログインしてみる

local%$ mssh ubuntu@i-0f123456abcdefg --profile <profile> --region ap-northeast-1

一見、誰しもが ubuntu でログインしていて監査が不安になりますが、 CloudTrail はちゃんと誰がログインしたか見ています。

CloudTrail

f:id:kenzo0107:20190628160255p:plain
CloudTrail

以下イベントでログが残っています。

  • SendSSHPublicKey
  • DescribeInstances

SendSSHPublicKey の「イベントの表示」ボタンクリックで JSON が表示されますが、その中で、アクセス元 IP, IAM User Arn、アクセス先 インスタンスIDがわかります。

{
    "eventVersion": "1.05",
    "userIdentity": {
        "type": "IAMUser",
        "principalId": "ABCDEFGHIJK....",
        "arn": "arn:aws:iam::123456789012:user/hogehoge",
        "accountId": "123456789012",
        "accessKeyId": "AKIxxxxxxxxxxxxxxxx",
        "userName": "hogehoge",
        "sessionContext": {
            "attributes": {
                "mfaAuthenticated": "false",
                "creationDate": "2019-06-28T06:18:50Z"
            }
        }
    },
    "eventTime": "2019-06-28T06:18:51Z",
    "eventSource": "ec2-instance-connect.amazonaws.com",
    "eventName": "SendSSHPublicKey",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "xx.xxx.xxx.xxx",
    "userAgent": "aws-ec2-instance-connect-cli/1.0.0 Python/2.7.16 Darwin/18.2.0 Botocore/1.12.179",
    "requestParameters": {
        "instanceId": "i-0f.......",
        "osUser": "ubuntu",
        "SSHKey": {
            "publicKey": "ssh-rsa AAAAB....rHb"
        }
    },
    "responseElements": null,
    "requestID": "01234567-890a-1234-5b6d-......",
    "eventID": "f51...",
    "eventType": "AwsApiCall",
    "recipientAccountId": "123456789012"
}

こちらで EC2 インスタンスのアクセス履歴等はわかります。

まとめ

これまで ssh アカウント管理は手間でしたが、IAM 権限での管理によって非常に楽になりました♪

CloudTrail で監査もバッチリ!

Nginx IP 直アクセス不許可 & LB ヘルスチェック設定

f:id:kenzo0107:20190424103347p:plain

AWS ECS で運用してるときに、よく設定している Nginx の configure file のアクセス元によっての振り分け方をまとめました。

ALB → Nginx → Rails

とはいえ、AWS ECS だけでなく、この設定は使うので、試していただけたらと思います。

Nginx 設定

  • conf.d/default.conf
# cannot allow ip direct
server {
  listen       80;
  server_name  _;
  return       444;
}

# healthcheck from LB
server {
  listen 80 default_server;
  listen [::]:80 default_server;
  root /work/app/public;

  location = /healthcheck.html {
    access_log  off;
    proxy_pass http://puma;
  }
}

server {
  listen  80;
  server_name example.com;
  ...

IP 直アクセス禁止

server_name _ とすることで、ip 直アクセスをターゲットにしています。

server {
  listen       80;
  server_name  _;
  return       444;
}

LB からのヘルスチェック

LB からヘルスチェックを向ける先を default_sever 設定することで、この server ディレクティブを参照します。

server {
  listen 80 default_server;
  listen [::]:80 default_server;
  root /work/app/public;

  location = /healthcheck.html {
    access_log  off;
    proxy_pass http://puma;
  }
}

上記 config file は、AWS ALB のヘルスチェックパスを /healthcheck.html とし、その向け先を Rails puma にしています。

Rails 側で以下の様に gem 'ok_computer' に向けるのも良し、独自にレスポンス返すも良しです。

get 'healthcheck.html', to: 'ok_computer/ok_computer#index'

ドメイン指定

example.com でアクセスされた際にこちらの server ディレクティブを参照します。

server {
  listen  80;
  server_name example.com;
  ...

ドメイン指定の悪い例

以前は以下の様に指定し、ip 直アクセス、ヘルスチェック対応していました。

server {
    listen 80;
    server_name example.com;

    if ($host != "example.com") {
        return 444;
    }

    location = /healthcheck.html {
      access_log  off;
      proxy_pass http://puma;
    }
    ...
}

勿論これでも動作します。ですが、やや可読性が悪いです。

マルチドメインでの IP 直アクセス不許可に対応をする際にも、この if 文がどんどん長くなります。

その為、向け先の意図毎に server {} を小まめに分ける運用の方が可読性が高く、実運用していてメンテナンサビリティが高いと感じました。

以上 参考になれば幸いです。

Terraform 運用ベストプラクティス 2019 ~workspace をやめてみた等諸々~

f:id:kenzo0107:20190417103456p:plain

以前 terraform で workspace 毎に tfstate 管理する方法を執筆しましたが、実運用上いくつかの問題がありました。

結論、現在は workspace 運用をやめています。

kenzo0107.hatenablog.com

workspace 運用例

まずは実際の運用例です。

もっとうまいことやってるぞ!という話はあろうかと思いますが、まずはありがちなケースを紹介します。

例) セキュリティグループ作成

以下の要件を実現するセキュリティグループを作成するとします。

要件

  • stg では、社内で Wifi の ip からのみアクセス可
  • prd では、ip 制限なくアクセス可

サンプルコード

  • variables.tf
variable "ips" {
  type = "map"
  default = {
    stg.cidrs    = "12.345.67.89/32,22.345.67.89/32"
    prod.cidrs   = "0.0.0.0/0"
  }
}
  • security_group.tf
resource "aws_security_group" "hoge" {
  name        = "${terraform.workspace}-hoge-sg"
  vpc_id      = "${aws_vpc.vpc_main.id}"
}

resource "aws_security_group_rule" "https" {
  security_group_id = "${aws_security_group.hoge.id}"
  type              = "ingress"
  from_port         = 443
  to_port           = 443
  protocol          = "tcp"
  cidr_blocks       = ["${split(",", lookup(var.ips, "${terraform.workspace}.cidrs"))}"]
}

resource "aws_security_group_rule" "https" {
  security_group_id = "${aws_security_group.hoge.id}"
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
}

実際に terraform を plan/apply する前にまずは terraform workspace を定義する必要があります。

terraform workspace new stg // 既に作成されていたらエラーとなります。
terraform workspace select stg

// terraform workspace = stg とした場合の tfstate をローカルのメモリ上で管理します。
terraform init 

上記のような処理があって、初めて、 variable "ips"stg.cidrs, prd.cidrs が利用できるようになります。

こちらを運用しようとしてみると以下の様な問題にぶつかりました。

実運用との相性が悪い

検証の為、ステージングのみに反映させたい、という時にどう運用したら良いでしょうか。

ステージング用、本番用に設定して、プルリクエストが通って、サンプルのコードを master にマージしていたらどうでしょう?

本番にもデプロイして良さそうに見えます。

いや、むしろ反映されていなければ、混乱します。

その後に master にマージして、本番に反映させたいコードが会った時に、サンプルコードの部分は反映させたくない!と言っても反映されてしまいます。

かといって、以下の様なコードを複数リソースに入れていくのは、余計なステップ数も増え、脳内でリソースが消費されます。レビューするのも辛いです。

count = "${terraform.workspace == "stg" ? 1: 0}"

では、本番用は設定しなければいいじゃないか!と言って設定しないと、本番用はエラーを出す様になり、その他の反映が何もできなくなります。

これはステージングも本番も同じファイルを参照している為に発生しています。

また、以下の様な workspace を利用していると以下の様な問題もありました。

stg, prd 以外に新たに workspace を追加したい場合

以下要望があった場合にどうでしょうか。

  • 負荷試験をする為に本番同様の環境を用意してください」
  • 「外部 API との連携試験をしたいので環境を別途増やして欲しいです!」

例えば、 負荷試験環境を用意しようとすると、 loadtst という workspace を用意するとしたら variables.tf を以下のように修正が必要です。

variable "ips" {
  type = "map"
  default = {
    loadtst.cidrs = "12.345.67.89/32,22.345.67.89/32" // 追加
    stg.cidrs     = "12.345.67.89/32,22.345.67.89/32"
    prod.cidrs    = "0.0.0.0/0"
  }
}

上記例ですと variable "ips" に 1行加えただけで良いですが、実際は あらゆる変数に loadtst.*** = *** というコードを追加していく必要があります。

workspace が増える毎に step 数が増え、ファイルの見通しが悪くなります。

また、以下の様なコードがあると、こちらも脳内リソースを消費し、疲弊します。

lookup(var.ips, "${terraform.workspace}.cidrs")
"${terraform.workspace == "stg" ? hoge: moge}"

workspace 運用をまとめると

workspace の利用はリソースを複数環境で共有する ことで運用する想定の為に、可読性の悪化、実運用との乖離がありました。

  1. 新たに workspace 追加する際に、全ての変数 map に追加しなければならない。
    → コードの見通しが悪くなる。
    → 新規環境の構築難易度が上がる。

  2. ステージングのみに反映という時の実運用が困難
    → ステージングも本番も同じファイルを参照している為、ファイルの中でステージングの場合は?と処理を分ける必要が出てきてしまう。

  3. 今、どの workspace なのかがわかりずらく、 terraform apply する際にかなり躊躇してしまう。
    → 実際 terraform apply 実行前に terraform workspace show で workspace 確認しても、実行中で少し時間が経つと、「あれ?どっちだっけ?」と不安になり、 Terminal を遡って確認することがあったりしました。

ではどうすると良いか?

徹底的に workspace をやめます。

= DRY な設計しよう!

これに尽きます。

実際にどうしたか以下まとめました。

ディレクトリ構成は以下のようにしました。

modules/common ... stg, prd どちらの環境でも共通して同構成で作成するリソースを置きます。

modules/stg,prd ... 個々に異なる構成となるリソースを置きます。*1

.
├── README.md
├──envs/
│   ├── prd
│   │   ├── backend.tf
│   │   ├── main.tf
│   │   ├── provider.tf
│   │   ├── region.tf
│   │   ├── templates
│   │   │   └── user-data.tpl
│   │   └── variable.tf
│   └──stg/
│       ├── backend.tf
│       ├── main.tf
│       ├── provider.tf
│       ├── region.tf
│       ├── templates
│       │   └── user-data.tpl
│       └── variable.tf
│
└──modules
    ├── common
    │   ├── bastion.tf
    │   ├── bucket_logs.tf
    │   ├── bucket_static.tf
    │   ├── certificate.tf
    │   ├── cloudfront.tf
    │   ├── cloudwatch.tf
    │   ├── codebuild.tf
    │   ├── codepipeline.tf
    │   ├── network.tf
    │   ├── output.tf
    │   ├── rds.tf
    │   ├── redis.tf
    │   ├── security_group.tf
    │   └── variable.tf
    ├── prd
    │   ├── admin.tf
    │   ├── admin_autoscaling_policy.tf
    │   ├── api.tf
    │   ├── app.tf
    │   ├── ecr.tf
    │   ├── iam_ecs.tf
    │   ├── output.tf
    │   ├── variable.tf
    │   └── waf.tf
    └── stg
        ├── admin.tf
        ├── api.tf
        ├── app.tf
        ├── ecr.tf
        ├── iam_ecs.tf
        ├── output.tf
        ├── variable.tf
        └── waf.tf

例のセキュリティグループの作成を例にするとどうなるか

以下の様になります。

  • envs/prd/variables.tf
variable "cidrs" {
  default = [
    "0.0.0.0/0",
  ]
}
  • envs/stg/variables.tf
variable "cidrs" {
  default = [
    "12.345.67.89/32",
    "22.345.67.89/32",
  ]
}
  • envs/common/security_group.tf
resource "aws_security_group" "hoge" {
  name        = "${terraform.workspace}-hoge-sg"
  vpc_id      = "${aws_vpc.vpc_main.id}"
}

resource "aws_security_group_rule" "https" {
  security_group_id = "${aws_security_group.hoge.id}"
  type              = "ingress"
  from_port         = 443
  to_port           = 443
  protocol          = "tcp"
  cidr_blocks       = ["${var.cidrs"))}"]
}

resource "aws_security_group_rule" "https" {
  security_group_id = "${aws_security_group.hoge.id}"
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
}

もし stg だけに反映させたいセキュリティグループであれば、 envs/stg/security_group.tf に作成したいセキュリティグループを記述します。

これで stg だけ反映という実運用をカバーできます。

また、負荷試験環境 ( loadtst ) という環境を用意したい場合は、以下の様にコピーし、変数を修正すれば良いです。

  • envs/prdenvs/loadtst
  • modules/prdmodules/loadtst

多少構成に変更があろうとも、 loadtst 関連のリソースが prd, stg に影響することはない様に作成できます。

terraform コーディングルール

以下のような workspace の切り替えを利用したコードを利用しないことです。

lookup(var.ips, "${terraform.workspace}.cidrs")
"${terraform.workspace == "stg" ? hoge: moge}"

また、以下も NG とします。 stg だけ異なるのであれば、 modules/stg,prd と分けるべきです。

"${var.env == "stg" ? hoge: moge}"

terraform 実行手順

stg, prd 各環境構築は envs/stg, envs/prd ディレクトリに移動し、 以下実行します。

terraform init
terraform get -update
terraform plan
terraform apply

AWS credentials の扱い

stg, prd で同じ AWS Account を利用する場合、プロジェクトの root に direnv 等、 .envrc を置いて、運用するのが良いと思います。

stg, prd で異なる AWS Account を利用する場合、 envs/(stg,prd) 以下に .envrc をそれぞれ配置し、上記 terraform 実行手順 を実行すれば良いです。

プロジェクト毎の terraform バージョンの違いの対応

tfenv で対応します。

macOS%$ brew install tfenv

以前の執筆記事では terraform を one-off container で実行しバージョン差異を吸収する様にしていましたが、コマンドが長くなり、管理も煩雑になるので、tfenv が望ましいです。

こちらも運用してみての実感です。

その他

これはしといた方がオススメ?レベルですが、 provider で バージョン固定外した方が良かったです。

provider aws {
  version = "1.54.0"
  region  = "ap-northeast-1"
}

固定されていて、最新のリソースが利用できない時があります。*2

その時は、バージョン固定でなく、アップデートしていく方向で修正した方が、最新に追従できます。

総評

実運用をしてみて、 workspace はやめておいた方がいいかなと感じたことをまとめました。

勿論、 workspace の良さを知り尽くしてないからこういう意見になっているとも思いますので、一概に否定する意図はありません。

リポジトリの整理がついたら現段階で公開できるところをしていこうと思います!

以上 Terraform 運用されてる方の知見になりましたら幸いです。

Infrastructure as Code ―クラウドにおけるサーバ管理の原則とプラクティス

Infrastructure as Code ―クラウドにおけるサーバ管理の原則とプラクティス

*1:ECS + RDS + Redis 構成で CodePipeline からデプロイするサンプル terraform です。

*2:Aurora MySQL が作れない!と思ったら、バージョン固定してた為だったことがありました。