出力を入力へ

プログラミングに関する基本的な事を中心にまとめます

TerraformでAWS管理ポリシー/カスタム管理ポリシー混合のロールを作成する

なんで悩んでいたのかわからない.

ロールにアタッチするポリシーのうち, AWS管理ポリシーをアタッチする場合は data.aws_iam_policy で対象のarnを指定する, カスタム管理ポシリーをアタッチする場合は resource.aws_iam_policyでポシリーを作成してarnを指定する. アタッチはどちらも resource.aws_iam_policy_attachment を利用すればよい.

dataとresourceをきちんと理解していなかった.反省.

resource "aws_iam_role" "terraform_sts_role" {
  name = "terraform-sts-role"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}

resource "aws_iam_policy" "custom_s3_list_bucket" {
  name = "CustomS3ListBucket"
  path = "/"

  policy = <<POLICY
{
  "Statement": [
    {
      "Action": [
        "s3:ListAllMyBuckets",
        "s3:ListBucket",
        "s3:HeadBucket"
      ],
      "Effect": "Allow",
      "Resource": "*",
      "Sid": "VisualEditor0"
    }
  ],
  "Version": "2012-10-17"
}
POLICY
}

resource "aws_iam_policy_attachment" "attach_custom_policy_to_sts_role" {
  name       = "CustomS3ListBucket"
  policy_arn = aws_iam_policy.custom_s3_list_bucket.arn
  roles      = [ aws_iam_role.terraform_sts_role.name ]
}


data "aws_iam_policy" "ec2_read_only_access" {
  arn = "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess"
}


resource "aws_iam_policy_attachment" "attach_ec2_read_only_access_to_sts_role" {
  name       = "EC2ReadOnlyAccessAttachment"
  policy_arn = data.aws_iam_policy.ec2_read_only_access.arn
  roles      = [ aws_iam_role.terraform_sts_role.name ]
}

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

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

書評:DNSがよくわかる教科書

DNSはなんとなくはわかるけど詳しいことは理解できていない, という状況の中で発売され評判もよかった本. やっとのことで読む時間を確保できたので読んだけど, 実際すごくよかった.

概要

DNSの仕組みから運用ノウハウまでを解説した本. 大きく3部構成で,DNSとは何かから学べる基礎編, 実際にネットワークを構築しDNS設定を行う設定やDNSを運用するときに必要な知見を記載した実践編, 運用のノウハウやセキュリティまわりの動向を扱ったアドバンス編で構成されている.

よかったところ

正確な表現を利用しつつも,わかりやすい解説があったことが良かった. インターネット上で調べると用語や説明がわかりにくいか,場合のよっては間違えているものが多く 理解の妨げになっていたけど, 世の中における誤りを指摘しつつ正しい用語で説明してくれたので理解が進んだ. その中でもわかりやすさを重視して解説してくれたおかげで, 途中で理解が迷子にならず読み進めることができた.

また,教科書というだけあって,一通りのトピックを扱っているので 知識の抜けがなく全体を俯瞰できたのはよかった. 加えてよくある誤りや設定ミスも記載されており, どういった点に注意すればよいかも理解しやすい.

8章のDNSの動作確認では実際にコマンドを使ってDNSの動きを理解できたこと, 9章のDNSに関するサイバー攻撃の分類と解説, 11章のDNS設定でよくある誤り, は特に参考になった.

よくなかったところ

全体をコンパクトにまとめることを重視したせいかもしれないけれど, もう少し説明が欲しい章はいくつもあった. 個人的には特にリソースレコードやDNSSECはもう少し詳しく扱って欲しかった. DNSの動作検証用のサイトとして取り上げた3つのDNSチェックサイトも どう見ればいいのか理解できていないでいる.

まとめ

DNSに対する苦手意識は払拭できたし, 今後どこの技術領域を調べればいいのかわかるようになった という点で非常によかった. インフラ/ネットワークを扱う人なら誰でも1度はDNSで つまずいた経験はあると思うので, そういう人には間違いなくお勧めできる. 基礎編はインフラを扱う人でなくても1度読んでおいて欲しいくらい.

GitHub Actionsのsteps.withディレクティブでは環境変数ではなく出力パラメータを利用する

タイトルがすべて.

steps.with ディレクティブにおける制御

GitHub ワークフローにてActionsを呼び出すとき, Actionsのパラメータ設定としてsteps.withディレクティブを利用する.

例えば,GitHubのリリースページにビルド成果物をアップロードする Upload Release Asset Actions では以下のように指定する.

jobs:
  build:
    name: Upload Release Asset
    runs-on: ubuntu-latest
    steps:
      ...
      - name: Upload Release Asset
        id: upload-release-asset 
        uses: actions/upload-release-asset@v1.0.1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: ./my-artifact.zip
          asset_name: my-artifact.zip
          asset_content_type: application/zip

ここで, steps.withディレクティブ以下の upload_url, asset_path, asset_name, asset_content の4つが upload-release-asset Actionsの入力パラメータである.

入力パラメータの動的制御

Releaseページにアップロードするファイルを指定する asset_path において, ビルドソフトウェアのバージョンが含まれたファイルを指定したい. ソフトウェアバージョンを環境変数に設定したとして, 以下のような利用方法を想定する.

jobs:
  build:
    name: Upload Release Asset
    runs-on: ubuntu-latest
    steps:
      ...
      - name: Set software version
        run: |
          VERSION=$(cat VERSION)
          echo ${ VERSION }
      - name: Upload Release Asset
        id: upload-release-asset 
        uses: actions/upload-release-asset@v1.0.1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: ./my-artifact-${ VERSION }.zip
          asset_name: my-artifact-${ VERSION }.zip
          asset_content_type: application/zip

このように,VERSIONファイルに格納されているソフトウェアバージョンをVERSION変数に格納し, ビルド成果物の制御にこの値を利用したい. しかし,この方法には以下の2つ問題がある

このため,Set software versionのステップにおける echo ${ VERSION }では期待した通りの値が出力されるが, Upload Release Assetのステップではそんなファイル存在しない (##[error]ENOENT: no such file or directory )と怒られてしまう. これは,VERSION 変数が設定されていない問題と,仮に設定されていたとしてもその変数を利用できない問題の両方を解決する必要がある.

出力パラメータによる制御

ではどうするかと言うと環境変数ではなく 出力パラメータ(output parameter)を利用しましょう,というのが解決策である. 出力パラメータは別のステップから出力結果を参照するための仕組みであり, steps.withディレクティブでも利用できる.

jobs:
  build:
    name: Upload Release Asset
    runs-on: ubuntu-latest
    steps:
      ...
      - name: Set software version
        id: software_version
        run: |
          VERSION=$(cat VERSION)
          echo ::set-output name=version::${ VERSION }
          echo ${ VERSION }
      - name: Check output parameter value
        run: |
          echo ${{ steps.software_version.outputs.version }}
      - name: Upload Release Asset
        id: upload-release-asset 
        uses: actions/upload-release-asset@v1.0.1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: ./my-artifact-${{ steps.software_version.outputs.version }}.zip
          asset_name: my-artifact-${{ steps.software_version.outputs.version }}.zip
          asset_content_type: application/zip

このように, Set software version ステップにて VERSION変数の値を出力パラメータに設定しているので, 後段の Check output parameter value では最初に設定した VERSION 変数と同じ出力を確認することができ, Upload Release Assetステップの asset_pathasset_name でも利用することができる.

Requestsで取得したwebサイトの文字化けに対処する

Requestsで取得したWebサイトをlxmlスクレイピングしようとすると, いくつかのサイトで文字化けすることに気が付いた.

これに対処するのに苦労したし,何ならまだ完全には解決できていない. 間違いとかあれば教えて欲しい.

文字化けの確認

例えば O'reillyのサイトだと文字化けが発生する. webサイトのタイトルを出力するPythonスクリプトは以下の通り.

import requests
import lxml.html

res = requests.get('https://www.oreilly.co.jp/books/9784873118864/')
root = lxml.html.fromstring(res.content)

print(root.xpath('//title')[0].text)

このスクリプトの実行結果は以下の通り

O'Reilly Japan - ã¬ã¬ã·ã¼ã³ã¼ãããã®è  ´

上記サイトはタイトルに「O'Reilly Japan - レガシーコードからの脱却」が指定されており, この通り出力して欲しい.

このブログでもダメ.

'å\x87ºå\x8a\x9bã\x82\x92å\x85¥å\x8a\x9bã\x81¸'

一方で,Yahooニュースなら正常に動作する.

'Yahoo!ニュース'

原因

もちろんエンコーディングの指定誤りが原因で, Requests で取得したwebサイトの文字コードを正しく識別できていないことが原因. これについては,以下のサイトが詳しい.

orangain.hatenablog.com

実際,HTTPレスポンスを確認すると

$ wget --server-response https://www.oreilly.co.jp https://www.oreilly.co.jp/books/9784873118864/
...
HTTP request sent, awaiting response... 
  HTTP/1.1 200 OK
  Date: Sat, 12 Oct 2019 16:42:53 GMT
  Content-Type: text/html
  Content-Length: 23094
  Connection: keep-alive
  Server: Apache
  Last-Modified: Thu, 10 Oct 2019 05:06:47 GMT
  ETag: "5a36-594875e4b2e01"
  Accept-Ranges: bytes
  Vary: Accept-Encoding

...

$ wget --server-response https://thaim.hatenablog.jp/ 
...
HTTP request sent, awaiting response... 
  HTTP/1.1 200 OK
  Server: nginx
  Date: Sat, 12 Oct 2019 16:25:25 GMT
  Content-Type: text/html; charset=utf-8
  Transfer-Encoding: chunked
  Connection: keep-alive
  Vary: Accept-Encoding
  Vary: User-Agent, X-Forwarded-Host, X-Device-Type
  Access-Control-Allow-Origin: *
  Content-Security-Policy-Report-Only: block-all-mixed-content; report-uri https://blog.hatena.ne.jp/api/csp_report
  P3P: CP="OTI CUR OUR BUS STA"
  X-Cache-Only-Varnish: 1
  X-Content-Type-Options: nosniff
  X-Dispatch: Hatena::Epic::Web::Blogs::Index#index
  X-Frame-Options: DENY
  X-Page-Cache: hit
  X-Revision: a7694746800267be0e2d318311d7b13e
  X-XSS-Protection: 1
  X-Runtime: 0.042860
  X-Varnish: 34747135
  Age: 0
  Via: 1.1 varnish-v4
  X-Cache: MISS
  Cache-Control: private
  Accept-Ranges: bytes
...

$ wget --server-response https://news.yahoo.co.jp 
...
HTTP request sent, awaiting response... 
  HTTP/1.1 200 OK
  Cache-Control: private, no-cache, no-store, must-revalidate
  Content-Type: text/html;charset=UTF-8
  Date: Sat, 12 Oct 2019 16:26:09 GMT
  Set-Cookie: B=1qlmjdleq3vl1&b=3&s=l0; expires=Tue, 12-Oct-2021 16:26:09 GMT; path=/; domain=.yahoo.co.jp
  Vary: Accept-Encoding
  X-Content-Type-Options: nosniff
  X-Download-Options: noopen
  X-Frame-Options: DENY
  X-Vcap-Request-Id: 9826dce8-3f25-4d7b-7749-c0e9fdcd8fba
  X-Xss-Protection: 1; mode=block
  Age: 0
  Server: ATS
  Transfer-Encoding: chunked
  Connection: keep-alive
  Via: http/1.1 edge2502.img.umd.yahoo.co.jp (ApacheTrafficServer [c sSf ])
  Set-Cookie: XB=1qlmjdleq3vl1&b=3&s=l0; expires=Sat, 19-Oct-2019 16:26:09 GMT; path=/; domain=.yahoo.co.jp; secure; samesite=none
...

そう,実ははてなブログではContent-Typeが適切に設定されているのに上手くいかない. 文字コードを小文字で指定しているのが原因かとも思ったけれど RFCによるとどちらでもよいみたい.

tools.ietf.org

requestsは chardet文字コードの 推定も行っているので確認してみたけど, ブログもYahooニュースもどちらもUTF-8を認識している.

>>> import requests, lxml.html
>>> res = requests.get('https://thaim.hatenablog.jp/')
>>> res.encoding
'utf-8'
>>> res.apparent_encoding
'utf-8'
>>> res = requests.get('https://news.yahoo.co.jp/')
>>> res.encoding
'UTF-8'
>>> res.apparent_encoding
'utf-8'

これについてはお手上げで, なぜYahooニュースでは上手くいくのに はてなブログでは上手くいかないのかわからなかった.

対策

根本原因がどうであれ, 正しく文字コードを指定して処理すればいいだけなので, この問題を解決するだけならlxmlでスクレイピングする前に文字コードを指定してデコードしてあげればよい.

意図した通り動作するスクリプトは以下の通り.

import requests
import lxml.html

res = requests.get('https://www.oreilly.co.jp/books/9784873118864/')
root = lxml.html.fromstring(res.content.decode('utf-8'))

print(root.xpath('//title')[0].text)

ここでは,UTF-8で固定しているけれど, res.apparent_encoding を指定したり,HTML内で文字コードを指定されているのであればそれに従う方法もある.

Terraform backendsのパラメータ化にPartial Configurationを利用する

Terraform Backends

対象インフラの状態を管理するtfstateファイルをチーム共有・管理するためにbackendを利用する.

www.terraform.io

例えば,AWS S3をバックエンドに利用する例は以下の通り. これによりS3でtfstateファイルを管理できる.

terraform {
  backend "s3" {
    bucket = "my-tfstate"
    key    = "sample-project/prod.tfstate"
    region = "ap-northeast-1"
  }

  required_version = "= 0.12.2"
}

backendにおける設定のカスタマイズ

上記設定においてバケット名やkey(ファイル名)をパラメータ化して実行時に指定したい.

バケット名はGitで公開したくない,keyは開発/検証/本番 環境によって切り替えたい といった要望に対応するため,各種設定項目に変数を利用したい. 場合によってはアクセスキーやシークレットキーの指定も必要で, その場合は特にGit以外で管理する必要がある.

これを解決するために,パラメータ部分に変数を利用したい

tf_backend_bucket = "my-ftstate"
tf_backend_key    = "sample-project/prod.tfstate"
variable "tf_backend_bucket" {}
variable "tf_backend_key" {}

terraform {
  backend "s3" {
    bucket = var.tf_backend_bucket
    key    = var.tf_backend_key
    region = "ap-northeast-1"
  }

  required_version = "= 0.12.2"
}

しかし,このような方法でterraformを実行してもエラーになる. backendの設定には変数は利用できない.

$ terraform init

Error: Variables not allowed

  on main.tf line 3, in terraform:
   3:     bucket = var.tf_backend_bucket

Variables may not be used here.


Error: Variables not allowed

  on main.tf line 4, in terraform:
   4:     key    = var.tf_backend_key

Variables may not be used here.

解決策

バックエンドのパラメータ化には変数ではなく Partial Configuration を利用する. Partial Configurationでは,terraform init実行時のオプションとして -backend-config を指定する.

  -backend-config=path This can be either a path to an HCL file with key/value
                       assignments (same format as terraform.tfvars) or a
                       'key=value' format. This is merged with what is in the
                       configuration file. This can be specified multiple
                       times. The backend type must be in the configuration
                       itself.

key=value 形式で指定するなら以下の通り:

$ terraform init -backend-config="bucket=my-tfstate" \
                 -backend-config="key=sample-project/prod.tfstate"

設定ファイルに書くなら以下の通り:

$ terraform init -backend-config="backend.tfvars"
bucket="my-tfstate"
key="sample-project/prod.tfstate"

このとき,backendの設定は空欄とする.

terraform {
  backend "s3" {
    region = "ap-northeast-1"
  }

  required_version = "= 0.12.2"
}

あらかじめデフォルト値を設定しておいてもよい. -backend-configオプションで指定すると上書きできる.

また,tfファイルで未設定のまま-backend-configで指定しないと インタラクティブに入力が求められる.

backendに Partial Configurationではなく 変数を利用したいというIssueは挙がっているけど, 対応される見込みはなさそう.

GoCon 2019 Springに参加しました

半年に1度のGo言語に関するカンファレンスのGo Conference 2019 Springに参加しました.

自分はGo言語は読む方がメインで,少し書く程度というGo言語初心者. そんな自分でもカンファレンス発表内容はGo言語自体の話だけではなく, Go言語を用いたソフトやシステムの話など幅広いトピックを扱っており, 非常に刺激になった.

gocon.jp

gocon.connpass.com

以下自分が聴講したセッション

keynote: Go Module Proxy Life of a Query

github.com

go modulesの話はなんとなく知っていた程度なので, 正直なところ概要しか理解できなかったのが残念だった. 発表のKatieさんの英語は聞きやすかったけど,前提知識が異なる場合は自分の英語力もまだまだだと実感した. 特にChecksum Databaseの話が難しかった.

前提知識としては,公式のブログ記事を 理解しておけばもう少し楽しめただろうか? とはいえ,Mirror (module proxy) の話は面白かったし, どの程度データ量等削減されるのかは試してみたい.

A1: Case Studies of designing developer friendly libraries

speakerdeck.com

内容としてはGo言語に限らない話で,かつGo言語の場合は特にどうなのかしっかりまとめられていた. なので自分みたいなGo言語以外をメインに利用している人には特に刺さると思った. 実際, context.Context の話とか自分の実装は適当だったなと反省した.

B2: エラー設計について / Designing Errors

docs.google.com

エラー設計はまさに悩んでいるところだったのでちょうど良い内容だった. failure とその設計思想を知れたのはよかった. 一方で自分で書くgoアプリは小規模なものなので,エラーハンドリング用パッケージの導入まで必要かは検討の余地がある. このあたりはいろいろ試行錯誤してみたい.

A3: Goによる外部プロセス起動ベストプラクティス及びtimeoutパッケージ徹底解決

songmu.github.io

goの外部プロセス停止に関する仕組みを調査・実装する上で, オフィシャルの見解の確認やGNU timeoutをベースとした知見の流用など, 開発の取り組み方がすばらしいと感じた. おまけにあたる終了コードを正しく取得するという内容も, 自分もきちんと考えていなかったことを再認識したので聞けて良かった.

A4: Design considerations for container-based Go applications

speakerdeck.com

どちらかと言うとコンテナの話がメインで,基本がしっかりと抑えられていた. 一方でGoの方はあっさりと結論だけ示されている形式だったので, Go初心者の自分としてはもう少しこのあたりの試行錯誤などが気になった. 実際に自分で実装するとき,他の実装方法やPros/Consがないかは自分でいろいろ考えてみたい.

A5: Expand observability in Go

docs.google.com

パフォーマンスチューニングの話は現状まったく取り組めていないので とっかかりになればいいなと考えていたけど, パフォーマンスチューニングの基礎から実際にどのように取り組めばよいか, デモ付きで話を聞けたのはすごくよかった. 忘れないうちに実践してみたいところ.

H6: Dive into Buildkit LLB with Go

speakerdeck.com

Dockerイメージ生成のはなしは概要レベルでしか把握できていなかったのでそれが ソースコードレベルで知れたのはよかった. Dockerfileの静的解析の話とかは度々出てくるので, 実際に手を動かして試してみるためのとっかかりになりそう. Docker / BuildKit は自分がGo言語を始めるきっかけでもあったので このレベルで理解できるようになりたい.

H7: We want AWESOME CLI tool & development

speakerdeck.com

Golden fileについては知らなかった. 参考となるリポジトリを複数挙げていたのは 実際に自分が実装するときの参考になるので助かる. 複数プラットホームにおけるHOMEの取得は, 標準でも対応しているみたいなので別途確認しておきたい.

B8: CPU, Memory and Go

speakerdeck.com

あまり詳細なGo言語の実装等の話になると理解できなくなるから困るなと心配していたけど, 計算機の基礎からC言語との比較までと,自分が理解できるちょうどいい内容だった. むしろ最近はこの手の話から遠ざかっていたのでいい復習になった. メモリの話がメインだったのでもう少しCPUや実行速度の話が気になった.

ansibleでansible-tower-cliモジュールを利用する

Ansible TowerCLIで制御するためのansible-tower-cli、 これをansibleで制御するansible-tower-cliモジュールの利用に苦労したのでそのまとめ。

ちなみに、動作検証に利用しているのはAnsible Towerではなく、そのOSS版のAWX。 動作環境は以下の通り

  • OS: Ubuntu 18.04
  • ansible: 2.7.10
  • tower-cli: 3.3.3

ansibleとansible-tower-cliの関係

当初の自分のイメージでは、 ansibleがtower-cliのライブラリを利用しつつtower-cliを意識することなく ansible towerを制御できるものだと思っていた。

ansibleのインベントリではansible towerを指定して動作する、 つまりは以下のようなイメージ。

f:id:thaim:20190412003936p:plain
誤解していたansible-tower-cliモジュールの動作

ところはこれは誤りで、当初ansibleを実行しても以下のようなエラーが表示されるだけで、 どうしてもansible-tower-cliモジュールが利用できていないことから悩んでいた。

FAILED! => {"changed": false, "msg": "ansible-tower-cli required for this module"}

ansibleのansible-tower-cliモジュールは単に tower-cliを実行する機能しか持たない。 ansible-tower-cliはansibleの実行ホストではなく、 接続先ホストにインストールしておく必要がある。 つまりは以下のようなイメージ。

f:id:thaim:20190412004222p:plain
実際のansible-tower-cliモジュールの動作

実際にはあえてansibleとansible-tower-cliの実行元を分ける必要はないので、 ansibleはローカルホスト接続して実行するとよさそう。 つまりは以下のようなイメージ。

f:id:thaim:20190412004758p:plain
ansible-tower-cliを用いた実際の構成

ansible-tower-cliのインストール方法

ansibleとansible-tower-cliの関係を確認している中で、 ansible-tower-cliのインストール方法も影響することがわかった。

ansible-tower-cliをグローバルインストールしている場合 (pip install --system ansible-tower-cli など)、 これは問題は発生しないが、 ローカルインストールした場合 (pip install --user ansible-tower-cli など)、 ansibleがansible-tower-cliモジュールを参照できない可能性がある。

ローカルインストールした場合にansibleからtower-cliが呼び出せるのは 両方とも同一バージョンのpythonインタプリタにインストールされた場合のみ。 つまりansibleもansible-tower-cliもpip install --userまたは、 pip3 install --userでインスートールした場合のみ動作する。

ansibleはapt installでインストールした場合や、ansibleはpip3でansible-tower-cliはpip、 またはansibleはpipでansible-tower-cliはpip3でインストールした場合は動作しない。

ansible-tower-cliはpipでしかリリースされていないので ansibleをapt installした場合はansible-tower-cliもpip install --systemでインストールする、 どうしてもuser installしたい場合はansibleと同じタイミングで同じ方法で インストールするように注意した方がよさそう。

tower-cliSSL検証

Ansible TowerをHTTPでホストする場合や自己署名のHTTPSでホストする場合は ansible-tower-cliからの接続でverify_sslオプションをfalseとする必要がある。

ansibleでansible-tower-cliモジュールを利用する場合は、 自動的に ~/.tower_cli.cfgが参照されるが、このverify_sslオプションのみ読み込まれず デフォルトのTrueが反映される。 一方で、ansible-tower-cliモジュールで明示的に設定ファイルを指定するとき (tower_config_fileでファイルを指定するとき)は設定ファイルで指定したverify_sslオプションが反映される。

個人的にはこの動作は非常に気持ち悪いが、 そもそもansibleの接続先のtower_config_fileを 明示的または暗黙的に参照するのはよくなさそう。

tower_config_fileは参照せず、ansible-tower-cliモジュールで 面倒でも明示的にホスト名やユーザ名などのオプションを指定した方がよさそう。