理系学生日記

おまえはいつまで学生気分なのか

忍者TOOLS

はてなBlogをHTTPS化したので HSTS、upgrade-insecure-requests あたりをまとめる

先日、はてなブログが HTTPS に対応したということで、このブログも HTTPS 化を実施しました。 このエントリを見ているとき、ブラウザのロケーションバーを見ると HTTPS になっていることが確認できると思います。

今日は、この HTTPS 化にからめて、混在コンテンツ、HTTP Strict Transport Security (HSTS) 、upgrade-insecure-requests あたりをまとめてみたいと思います。

HTTPS 化の流れ

そもそも、みなさんご存知のように、最近は全サイトを HTTPS 化しようという流れになっています。

下記のページに依ると、2018/04/14 における「Chrome で HTTPS 経由で読み込まれたページの割合」は、61 % になってたりします。

HTTPS 化への流れはセキュリティが主題ですが、これに伴い、SEO にも関連します。

混在コンテンツ

で、HTTPS 化するにあたって、必ず出てくるのがこの混在コンテンツ(mixed contents) の問題です。

混在コンテンツというのをざっくり言うと、HTTPS で提供されるページから HTTP 経由で読みこまれるリソースです。 こうなると、(Secure じゃない)Cookie の盗聴やら、中間者攻撃やらにさらされるので、基本的には忌むべき存在となります。

この混在コンテンツには、大きく以下の 2 種類に分けられます。

  • Optionally-blockable Content
  • Blockable Content

このあたりは、Mixed Content に定義があります。

Optionally-blockable Content は、HTTPS ページから読み込まれる <img><video><audio> といったリソースで、 各ブラウザは警告は出すものの、リソースを 読み込む という判断をしています。 セキュリティのリスクはあるはあるんですが、そのリスクよりも、「読み込めないと判断したときの影響」の方がでかいよね、って話です。

一方、Blockable Content はそれ以外のリソースで、代表的なのは <script> とかです。 こっちについては、ブラウザはセキュリティリスク高と判断して読み込みをブロックします。

頻繁に見ると思うんですが、以下が Chrome のコンソールに出力されるログですね。上が Optionally-blockable、下が Blockable。

f:id:kiririmode:20180419094536p:plain

HSTS

で出てきたのが HTTP Strict Transport Security (HSTS) です。

HSTS は簡単に言うと、サイトを閲覧するときに、HTTP ではなくぜったいに HTTPS にしようぜっていう仕組みです。 だいたいブラウザとサーバ間の挙動は以下のようになります。

ブラウザが HTTP のページにアクセスした場合

  1. ブラウザが HTTP のページにアクセスする
  2. サーバはステータスコード 301 でリダイレクト

→以下、次のフローに乗る

ブラウザが HTTPS のページにアクセスした場合

  1. ブラウザが HTTPS のページにアクセスする
  2. サーバは Strict-Transport-Security (STS)ヘッダを返す
    • twitter.com は strict-transport-security: max-age: 631138519
    • wikipedia.org は strict-transport-security: max-age=106384710; includeSubDomains; preload
  3. ブラウザは、対象ホストが:
    • まだブラウザ内に HSTS Host として登録されていない場合は、対象ホストを HSTS Host として登録する
    • ブラウザ内に HSTS Host として登録されている場合は、STS ヘッダの内容をブラウザ内に登録する

ミソは 3. で、この後、ブラウザが対象ホストにアクセスする場合は http ではなく意地でも https にアクセスします。 この辺りの挙動は Chrome だと分かりやすくて、以下が http://www.wikipedia.org にアクセスしたときのヘッダなのですが、 307 Internal Redirect で https にリダイレクトするようになっています。このリダイレクト、「Internal Redirect」の Phrase のとおり、Chrome 内部での疑似レスポンスです。

f:id:kiririmode:20180419125010p:plain

Firefox の場合はこの疑似リダイレクトとかは見えません。ロケーションバーに http://www.wikipedia.org と打ち込んでエンターを押しても、勝手に https でアクセスしたことになります。

あれ、少なくとも一度 HTTP にアクセスするならそこを MITM で狙われるのでは

これはそのとおりで、ブラウザが HSTS ホストとして認識していないホストにアクセスするときは、HTTP でアクセスせざるを得ないことが有り得ます。 この対策として、各ブラウザはそのリリース段階で多数のホストを HSTS ホストとして登録しています。上に挙げた twitter.com とか wikipedia.org とかはまさにその例です。 こうやって、一度たりとも HTTP を許さない方法を Preloaded HSTS とか呼んだりします。

この Preloaded HSTS の対象ホストの一覧は、

にあります。Chrome のリストに追加希望がある場合は、たしか HSTS Preload List Submission から申請できるはず。

HSTS ホストとして登録されているかを確認する方法

Preloaded HSTS ホストなら上記の一覧を見れば良いですが、Preloaded でないホストを含めて自分のブラウザを確認したい場合は、chrome://net-internals/#hsts にアクセスすれば良いです。 こんな感じでクエリできます。

f:id:kiririmode:20180419130050p:plain

upgrade-insecure-requests

上述の通り、完全 HTTPS 化できたサイトは HSTS 適用すれば良いんですが、一部 HTTP が残っちゃう、みたいな場合に役立つのが、この upgrade-insecure-requests です。 Content-Security-Policy (CSP) の一種なんですが、upgrade-insecure-requests の目的は、「HTTPS にマイグレーションするときの管理者/開発者の負荷を下げること」にあります。

挙動については、HTTP のレスポンスヘッダに Content-Security-Policy: upgrade-insecure-requests が含まれているページで、以下のような http 経由の画像参照があった場合、

<img src="http://example.com/image.png">
<img src="http://not-example.com/image.png">

ブラウザはあたかも以下のように書いてあるものと解釈して、画像のフェッチを実行します。

<img src="https://example.com/image.png">
<img src="https://not-example.com/image.png">

もちろん、これらの画像が HTTPS で取得できない場合は、http に fallback することなくネットワークエラーとして扱われます。

HSTS と競合するのでは?

上記のように,強制的に http -> https と変更するという意味では、HSTS と同じようにも思えるのですが、W3C 的にも upgrade-insecure-requests だと HTTP 通信が行われることを防げないことを記載しています。

Authors can and should continue to use that header to ensure that their users are not subject to SSL stripping downgrade attacks, as the upgrade-insecure-requests directive will not ensure that users visiting your site via links on third-party sites will be upgraded to HTTPS for the top-level navigation.

Upgrade Insecure Requests

参考資料

Terraformに入門してサーバを構築してみるまで

VPS を何年も前に解約してから Linux の遊び場もなくなってしまっていたのですが、 AWS を勉強したこともあり、あー EC2 なり S3 なり使えば色々できるなぁと思い立ちました。

せっかくなので、楽に遊び場を作ったり消したりしたいなぁということで、 Terraform に入門するかと思い立ったのが昨日のことです。

Getting Started あたりを読んで tf ファイルを書いていったところ、 目標だった任意台数の Web サーバをデプロイするところまではいけたので、苦難の道のりをまとめようと思います。

なにせ短期間しか Terraform に触れていないので、間違ってるところとかあったら教えてくださいおねがいします。

あと上級者の方は id:minamijoyoTerraform職人入門: 日々の運用で学んだ知見を淡々とまとめる - Qiita でこれでもかってくらいのヤツをまとめてくれているので、読んだら良いはずです。

目次記法ができたの知らなかった

今日のゴール

以下を達成することをゴールとしました。

  1. 1 コマンド
  2. 任意台数の Web サーバを構築できる
  3. Web サーバには、インターネットから ssh/http でアクセス可能

Terraform とは

Terraform は、安全かつ効率的に、インフラの構築、変更、バージョン管理をするためのツールとされています。

Terraform 自体は、ぼくから見ると「IaaS だろうが PaaS だろうが SaaS だろうが、API さえ提供してくれたら 下回りは作ってやるぜ」というような感じで、

  • AWS や Azure、GCS (このあたりはイメージつきやすいですね)
  • Heroku (このあたりもなんかイメージできますね)
  • Github や MySQL (??????????)

とか諸々下回りを作ってくれます。 Terraform の Github のドキュメントを見る限りリポジトリやチームメンバの登録とかできるようですし、 MySQL だと database やら grant やらの投入ができるっぽいです。

全体として見ると Ansible とかと領域が被っている気もするんですが、 Provisioner のドキュメントに目を通すかんじ、Ansible とか Chef よりも もう少し下位レイヤが主戦場なんだろうと読み取っています。EC2 とかの上で Web/App サーバとかを立てようとすると、Chef とかコマンド列を呼び出すことになりますし。

開発環境 (Emacs)

ぼくは Emacs 派ということもあり、terraform-mode をインストールしました。

terraform-mode は、パスを通すだけで、シンタックスハイライトはもちろん、 ファイル保存をフックにした terraform fmt の実行 (go fmt と同じようにコードフォーマットしてくれる) や auto-mode-alist への設定追加もしてくれるので、インストールしただけで何もしておりません。。。

開発環境 (Terraform)

Terraform 自体は、ver.up がわりと早いので、Terraform 本体ではなく、tfenv を入れました。 rbenv とか plenv と同種の、Terraform 用のバージョンマネージャです。

Terraform の最新バージョンをインストールしておきました。

$ tfenv list-remote | head -3
0.11.7
0.11.6
0.11.5

$ tfenv install 0.11.7

Terraform の重要概念の整理

とりあえず重要なのは、プロバイダとリソースだと思います。

リソースっていうのが、物理マシンとか VM とか Github リポジトリ上のメンバーとかです。

一方、プロバイダっていうのが、それらを API 経由で提供してくれる輩です。具体的には AWS とか GCP とか Github とかです。

これを理解した上で、とりあえず以下のファイルを main.tf として保存して、terraform を実行すれば、AWS "プロバイダ"の上で VPC "リソース"が構築されます。 各属性はともかく、AWS を使って、東京リージョン (ap-northeast-1) に 10.2.0.0/16 のネットワークを持つ VPC を作っていることが直感的に分かります。

terraform {
  required_version = "= 0.11.7"
}

provider "aws" {
  region = "${var.aws_region}"
}

resource "aws_vpc" "vpc" {
  cidr_block = "10.2.0.0/16"
  enable_dns_support = true
  enable_dns_hostnames = true
}

variable "aws_region" {
  default = "ap-northeast-1"
}

プロバイダは複数書くこともできて、1 つの tf ファイル上で GCP と AWS のリソースを一度に作り連携させる、なんてことも可能です。

リソースに依存したリソースはどうやって書くの?

リソースには依存関係がある場合があって、例えば VPC の中でサブネットを切る場合なんてのがその例です。 サブネットは VPC なしには構築できません。

サブネットリソースの定義も色々書けますが、ぼくはとりあえずこんなかんじにしました。

resource "aws_subnet" "global" {
  vpc_id = "${aws_vpc.vpc.id}"
  cidr_block = "${cidrsubnet(aws_vpc.vpc.cidr_block, 8, 4)}"
  map_public_ip_on_launch = true
}

ミソは vpc_id です。 ここでの定義は、「aws_subnet というサブネットを表現するリソースは、aws_vpc というリソースの vpc という名前の ID に紐付けられている」ということを示しています。 こういう紐付けをすると、aws_subnetvpc に依存しているという暗黙の依存関係が定義されます。

この依存関係は、terraform graph コマンドで見ることができまして、Graphivzdot コマンドを使って、

$ dot -Tpng <(terraform graph) -o dependency.png

とかで画像化すれば、aws_subnet.global から aws_vpc.vpc への依存を示す矢印が存在していることがわかります。

f:id:kiririmode:20180418011904p:plain:w500

こういった依存関係と resource を駆使して、必要なリソースを配置していけば、インフラの定義ファイルが完成します。

変数

サーバの数だったりリージョンだったり、可変にしておきたいところは変数にしておきます。

変数は variable で宣言できます。以下では、aws_region っていう、デフォルト値 ap-northeast-1 の変数を宣言してます。

variable "aws_region" {
  default = "ap-northeast-1"
}

この値は、terraform コマンドでも、環境変数でも上書きできます。 簡単に上書くのであれば、

$ terraform plan -var 'aws_region=us-east-1'

といった形で上書きできますし、変数値をまとめたファイルをコマンドに渡すこともできます。 環境変数でも上書きできて、

$ export TF_VAR_AWS_REGION=us-east-1

ってかんじで TF_VAR_ を接頭語につけた環境変数を宣言しちゃえば良いです。

便利関数

tf ファイルには、色々関数を使うことができて、わりと便利だったりします。 このあたりにまとまってる。 - Interpolation Syntax - Terraform by HashiCorp

サブネットの例を再掲しますが、ここでの cidrsubnet なんてのはまさに便利関数です。

resource "aws_subnet" "global" {
  vpc_id = "${aws_vpc.vpc.id}"
  cidr_block = "${cidrsubnet(aws_vpc.vpc.cidr_block, 8, 4)}"
  map_public_ip_on_launch = true
}

cidrsubnet(iprange, newbits, netnum) は、CIDR で示された iprange のネットワーク帯のうち、さらに newbits 分だけサブネットを切って、そのうちの netnum 番目のサブネットを返せ、っていう関数で、 ここでの iprange10.2.0.0/16 だったとすると、返却値は 10.2.4.0/24 になります。

他にも、file(path) は、パスで示されたファイルの中身を返してくれますし、色々できます。はい。 偉そうに書いていますが、あんまり把握できていません。はい。

サーバのプロビジョニング

リソースの作成、破棄時にスクリプトとかを実行することができて、その仕組みを provisioners と呼んでいます。 たとえば、想像しやすい例だと、remote-exec とうプロビジョナは、デプロイされるサーバ側で任意のコマンドを実行できます。がんばれば、ansible とかも実行できるはず。

ぼくは今回、httpd をインストールして起動するにあたり、この remote-exec を使いました。 見た方がイメージできると思うので、EC2 のサーバリソースの宣言を以下に示します。

resource "aws_instance" "svr" {
  # Amazon Linux AMI 2017.09.1 (HVM), SSD Volume Type
  ami = "ami-a77c30c1"
  count = "${var.server_num}"

  instance_type = "t2.micro"
  associate_public_ip_address = true
  subnet_id = "${aws_subnet.global.id}"
  vpc_security_group_ids = ["${aws_security_group.allow-ssh.id}"]
  key_name = "${var.key_name}"

  connection {
    host = "${self.public_ip}"
    type = "ssh"
    user = "ec2-user"
    private_key = "${file(var.key_file)}"
  }

  provisioner "remote-exec" {
    inline = [
      "sudo yum -y install httpd",
      "echo hello | sudo tee /var/www/html/index.html",
      "sudo service httpd start",
      "sudo chkconfig httpd on"
    ]
  }
}

こんな風に何でもできるといえばできるのですが、AWS 的には、予め httpd とかを組み込んだ AMI を作っておくほうが捗るように思います。サーバ作成時にプロビジョニングするの、時間かかるし。

任意個数のサーバ

同一構成のサーバを任意の個数並べたいなんてときは、count を使います。count については、Configuring Resources - Terraform by HashiCorp のあたりにシレっと説明がある。 上に貼り付けた EC2 サーバリソースにも使ってます。ただ、count = 5 とか書くだけで、5 台ほどサーバができるってかんじになります。

必要な情報を簡単に手に入れたい

サーバつくったら ssh したいんですけど、ssh しようと思ってもホスト名が分からん。 Elastic IP を付与したりすれば良いし、もちろんそれも Terraform でできるんですが、ここでは Elastic IP を付けてなかった。

terraform show で全量出しても良いんだけど、 こういうときに便利なのが output ってヤツです。便利なかんじで出力してよーという定義です。

output "address" {
  value = "${aws_instance.svr.*.public_dns}"
}

上の例では、「EC2 サーバの台数分、インターネットから見えるホスト名を出力してよー」という定義をしています。 これを加えて実行すると、terraform apply の最後に、

$ terraform apply -var 'server_num=2'
(snip)
Outputs:

address = [
    ec2-18-182-32-112.ap-northeast-1.compute.amazonaws.com,
    ec2-13-231-199-50.ap-northeast-1.compute.amazonaws.com
]

というようなホスト名が出力されるようになりますし、terraform output でも同様の出力が得られます。 json 形式でも出力可能で、スクリプトとかで捗りそうです。

$ terraform output -json | jq '.address.value'
[
  "ec2-18-182-32-112.ap-northeast-1.compute.amazonaws.com",
  "ec2-13-231-199-50.ap-northeast-1.compute.amazonaws.com"
]

というわけで

だいたい以下のようなかんじになりました。 変数はファイルを別にしているんですが、ここではマージしちゃっています。

コマンドちょっと打つだけで、自分の遊べるインフラが瞬時にでき、また、ぶっ壊せるというのはなかなかに新しい体験で、 正直かなりおもしろいです。まだ Getting Started を読んだ程度ですし、AWS にも疎いところなので、 Terraform、AWS ともにまだまだ勉強していきたいなと思っています。

terraform {
  required_version = "= 0.11.7"
}

provider "aws" {
  region = "${var.aws_region}"
}

resource "aws_vpc" "vpc" {
  cidr_block = "10.2.0.0/16"
  enable_dns_support = true
  enable_dns_hostnames = true
}

resource "aws_internet_gateway" "igw" {
  vpc_id = "${aws_vpc.vpc.id}"
}

resource "aws_route" "internet_access" {
  route_table_id = "${aws_vpc.vpc.main_route_table_id}"
  destination_cidr_block = "0.0.0.0/0"
  gateway_id  = "${aws_internet_gateway.igw.id}"
}


resource "aws_subnet" "global" {
  vpc_id = "${aws_vpc.vpc.id}"
  cidr_block = "${cidrsubnet(aws_vpc.vpc.cidr_block, 8, 4)}"
  map_public_ip_on_launch = true
}

resource "aws_security_group" "allow-ssh" {
  name = "dev-sg-allow-ssh"
  description = "Allow ssh for each server"
  vpc_id = "${aws_vpc.vpc.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"]
  }

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

resource "aws_instance" "svr" {
  # Amazon Linux AMI 2017.09.1 (HVM), SSD Volume Type
  ami = "ami-a77c30c1"
  count = "${var.server_num}"

  instance_type = "t2.micro"
  associate_public_ip_address = true
  subnet_id = "${aws_subnet.global.id}"
  vpc_security_group_ids = ["${aws_security_group.allow-ssh.id}"]
  key_name = "${var.key_name}"

  connection {
    host = "${self.public_ip}"
    type = "ssh"
    user = "ec2-user"
    private_key = "${file(var.key_file)}"
  }

  provisioner "remote-exec" {
    inline = [
      "sudo yum -y install httpd",
      "echo hello | sudo tee /var/www/html/index.html",
      "sudo service httpd start",
      "sudo chkconfig httpd on"
    ]
  }
}

output "address" {
  value = "${aws_instance.svr.*.public_dns}"
}

variable "aws_region" {
  default = "ap-northeast-1"
}

variable "key_name" {
  default = "testkey"
}

variable "key_file" {
  default = "~/key/testkey.pem"
}

variable "server_num" {
  default = 1
}

golangでHTTP_PROXY、http_proxy環境変数の差異を吸収する

HTTP_PROXY なのか http_proxy なのかという大文字・小文字問題は Golang において実装時にどのように吸収するべきなのか、という疑問があったのですが、 net/http/transport の実装方法がなるほどなぁというかんじでした。

まず、主役となるのは envOnce という private struct。 コメントはぼくが勝手に入れています。

type envOnce struct {
    names []string    // 揺れがある環境変数名のスライス。
    once  sync.Once   // おなじみの Once
    val   string      // 環境変数に設定された値
}

例えば HTTP_PROXYhttp_proxy の差異の吸収は、以下の変数で行われます。

   httpProxyEnv = &envOnce{
        names: []string{"HTTP_PROXY", "http_proxy"},
    }

環境変数の値の読込は、init メソッドで行います。

func (e *envOnce) init() {
    for _, n := range e.names {
        e.val = os.Getenv(n)
        if e.val != "" {
            return
        }
    }
}

環境変数名をループさせて、実際に設定値があるかどうかをチェックするっていう素直な実装です。

問題は、PROXY を取得する都度、init() を呼び出さないといけないのかってことで、それはちょっと面倒。 これを回避するのが once.Do です。

func (e *envOnce) Get() string {
    e.once.Do(e.init)
    return e.val
}

クライアントが HTTP_PROXY の値を取得したいときは、上記の Get を呼び出すことになるのですが、 Get の呼び出し都度、あたかも init が呼び出されるように見えますが、この都度呼び出しを once.Do が防いでいます。

once.Do は、その引数の関数を、プロセスのライフタイムにおいて一度のみしか呼び出さない (初回以降呼び出された場合は、即実行を打ち切る) ようになっています。 このため、Get の実装が上記のようになっていたとしても、init は一度しか呼び出されないということになります。

このあたりの書き方がぼくにとってすごく面白く思えたので、エントリまで起こしてしまいました…。

なお、HTTPS_PROXYNO_PROXY も同様に実装されています。

Real World HTTPを読んだ

Real World HTTP 読みました。

Real World HTTP ―歴史とコードに学ぶインターネットとウェブ技術

Real World HTTP ―歴史とコードに学ぶインターネットとウェブ技術

これまで、HTTP を真正面から扱った本というと、Web を支える技術が思い浮かぶんですが、こちらは刊行が 2010 年。

Webを支える技術 -HTTP、URI、HTML、そしてREST (WEB+DB PRESS plus)

Webを支える技術 -HTTP、URI、HTML、そしてREST (WEB+DB PRESS plus)

n Real World HTTP では、その後についに表れた HTTP 2 を含め、HTTP に関する歴史 (なんと 0.9 から!) とそこで定義されてきた規約、そして HTTP をベースにした様々な技術 (OAuth、OpenID Connect や RDF、WebSocket や Server-Sent Event など)、 そして、HTTP に関するセキュリティといった HTTP 関連情報を詰め込んだ本になっています。

HTTP というと、その裾野が広すぎて、とても全部を書籍に詰め込むことはできない(と思う)からこそ、これまで 関連技術を含めた HTTP というのは「つまみ食い」「必要になって勉強」せざるを得なかったように思うんですが、 この本に書いて技術を理解しておけば、HTTP に関してはなんとかなっていくかなぁと思うほどに、テーマとしては広範でした。

一方で、この本を手元に置いておけば HTTP は完全理解かというと、決してそんなことはなく、 リファレンスにするには内容が足りない(おそらくはリファレンスを想定した書籍でもない) ので、一つ一つの技術は深追いしていく必要は あると思います。思うのですが、「あぁ自分はこの技術に対する理解が足りないな」と気付きを与えてくれる書籍としてはすごく有用で、 どの RFC で定義されているのかがキッチリ書いてあり、自学の助けになると思います。 自分自身についても、よく知らない技術がいくつもあって、あーまだ勉強せんとなぁという気分にさせられました(HTTP 2 については、まったく RFC に目を通せてない…)。

特色としては、Golang を使った HTTP 系の実装コードが載っていることではあるんですが、 「Golang を使うときはこのモジュールを使えば良いんだ」は分かる一方で、 モジュールが良い感じに技術詳細をブラックボックスにしてくれているので、HTTP というよりは Golang の使い方が分かる、といったものかと 思います。

例えば、HTTP サーバがどのように実装されるのか、なんていうケースは、別の書籍を読んだ方が良いのかと思いました。 (最近、ソケットプログラミングとかだとどの書籍を読むんですかね…)

みんなのGo言語

こちらも積読してたのでようやく読みました。

みんなのGo言語[現場で使える実践テクニック]

みんなのGo言語[現場で使える実践テクニック]

  • 作者: 松木雅幸,mattn,藤原俊一郎,中島大一,牧大輔,鈴木健太
  • 出版社/メーカー: 技術評論社
  • 発売日: 2016/09/09
  • メディア: Kindle版
  • この商品を含むブログを見る

Golang の文法にはほとんど触れておらず、実践的なテクニックだったり、お勧めのライブラリだったりをカテゴリごとに説明していくタイプの書籍です。 このため、本を読む上では、Golang 自体の基本知識が要求されます。 そういう意味では、Perl best practice とかに似てるところがあるなぁと懐しいものを思い出しました。。

Perlベストプラクティス

Perlベストプラクティス

ぼく自身、Golang の経験が限りなく浅いこともあり、

を読んだあとで、こちらの書籍を読みましたが、なんとか読み切れたように思います。

内容としては、Golang の開発環境や Makefile の例から flag モジュールの実装とその拡張方法、 reflect モジュールのハマりポイントなどかなり濃い内容で、結構ワクワクしながら読めました。

このへんで、ちょっとずつアウトプットできれば良いんだけどな…。Golang、あと 2 冊積読あるんだけどな…。どうするかな…。

"What I Talk About When I Talk About Platforms" にプラットフォームを構築する上でのすごい良いことが書いてあった

最近、"プラットフォーム" というものについて考えることが多いのですが、martinfowler.com にすごい良い記事があがっていました。 記事の内容を忘れたくはないので、(自分にとって)大事だなーと思うことをこのエントリに残しておこうと思います。

ぼくがどこまで正確に理解しているのかは分かりませんので、正確な内容を知りたい方は、以下の記事を参照してください。

それでは。

まずはプラットフォームとは

ここでのプラットフォームの定義っていうのは、「ファイルシステムやセキュリティとかの再利用可能な機能をもったアプリケーションの動作環境」ってことになっています。 ThoughtWorks では、Digital Platform ってのを定義していてこの内容は多岐に渡るんですが、ここでのスコープは、その中の Delivery Infrastructure ってヤツが対象です。

何が問題なのか

著者が過去に経験したプロジェクトだと、インフラ・運用が技術でチーム分けされていたそうです。DBA、ミドルウェア、セキュリティ…というように。 もちろん個々のチームの中では、改善もガバナンスもコスト削減もなされるわけですが、全体としてはそれがなされないっていう問題がでてきます。

何か顧客の要望があったとしても、これは DBA チームに、これはネットワークチームに、といったチーム横断の依存関係が発生してしまって、小さな変更要望すら数ヶ月かかることもある有様。 さらに、エンジニアにもマネージャーにも、「本当に必要なことしか変更を受け付けない」というような雰囲気も蔓延してしまっていたようです。これにより、品質改善もリファクタも行われないから、さらに時間がかかるようになって…という悪循環。

どこもありますよね、コンウェイの法則だったり、"組織の壁を超えるのはオレの仕事じゃない"っていうパターン。いわゆるサイロ化が発生してしまっていました。

著者は、自己組織的なチームの動きを阻む一要因は "backlog coupling" であると述べています。 これは要するに、特定のチームに「ここをこうしたい」っていうバックログがあったとして、それを実現するには他のチームに一々依頼しなきゃいけない状況です。

どこぞの企業では、この backlog coupling が起こったときの生産性悪化は実に 10 - 12 倍に達したそうです。

やっぱりよくありますよね、こういう状況。依頼作業とか、そもそもその依頼の仕方を探すとか、そういうことに滅茶苦茶時間がかかるヤツ。 リードタイムを鑑みてスケジュールをたててみると、あれ、開発作業ってこんな短期間で実施しなきゃいけなかったんだ…とかですね。

プラットフォームが目指すべき方向

既に長くなりましたが、というわけで、「良いプラットフォームの特徴は、backlog coupling の量を減らすこと」というのが記事の趣旨だと思います。

そのためには、(他チームに)チケットを起票してアサインするみたいなことではなく、チーム自身で面倒を見れるようにすべきだし、そういう self-service のための機能をプラットフォームが提供すべきって話になっています。 これは決して固定的なインフラのテンプレートを提供するだとかで権能を開発チームには渡さないというわけではなく、開発チームに責任を持って判断してもらうということです。

個人的には、以下の記載がグッときました。

We call this type of approach a ‘superficial private cloud’ - re-labelling existing virtualisation platforms for use by delivery teams in a very constrained way, with no real intent to reduce the amount of centralised control.

(意訳) 我々はこの種のアプローチ -- 集権的コントロールを減らそうとすることもせず、デリバリチームに非常に制約された方法でしか触れさせないような、単なる「仮想基盤」のラベルを替えただけのインフラ -- を "表層だけのプライベートクラウド" と呼んでいる。

何が犠牲になるのか

AWS なんかそうだと思うんですが、機能はしっかり提供するから、アプリチームで作って動かして (You build it, You run it) っていう思想のプラットフォームの提供。

こうすることによって、アプリチーム自身が技術スタックに習熟していきますし、プロダクトの所有意識や責任感の醸成、チーム間の依存の排除などに寄与するようになり、 また、プロダクトに責任を持ち、かつ、難易度の高い問題解決に喜びを感じるエンジニアに、そのチームや企業が魅力的に映るようになるとのこと。

このあたりはピープルウェアにもあった。

一方で、アプリチームには、開発し運用する上でのあらゆる分野の判断・決断が必要になります。誰かがよろしくやってくれるから、おれは知らね、みたいなことは許容されにくくなります。これって結構でかいオーバヘッドで、 そもそも判断に足るスキル・知識を個々のアプリチームに用意できるのかってことや、いちいち判断してたら逆にスピードが出ないんじゃね?みたいなことになります。 アプリチームに要求されることって、多くなるんですね。

このため、生産性をあげていこうとすると、アプリチームが標準で使える「標準インフラ」を整備しようということになります。あれ、これってアプリチームの自律性を損なわせることにならね???

というわけで、この「自律性のもたらす多様化」と、(逐一考えさせるというオーバヘッドを減らすための)いわゆる"標準"をつくることのバランスをどうするかが一番難しいポイントであり、事前にはとても分かりません。 そして、そのバランスを見出していくためには、プラットフォームを使うことが魅力的でなければならないとされています。魅力的なプラットフォームはどういうものかっていうアイディアも書かれてはいるんですが、 結局は「自分でつくるよりも、提供されているものを使う方が良いよね」と開発チームに思ってもらうことが重要とか。

というわけで、成功のために必要な前提は 3 つ。

  1. プラットフォームの開発と運用を担当する長期的なチーム体制があること
  2. 中央集権的なオペレーションやサポートから離れ、多くの責任をアプリケーションチームに移譲することを真に願っていること
  3. アプリケーションチームへの自由と責任の移譲は、一貫性を犠牲にするということを理解していること

プラットフォームつくるのって大変だなぁと改めて思ったのでした。

最近目にする$HOME/.configというディレクトリ

最近、$HOME/.config に設定ファイルを配置するツールが多くて、これは一体どういうことなんだということを思っていました。git とか peco とかですかね。

たぶん、ls -al ~ とかするとファイル数おおくて収拾つかなくなることに対する問題意識からそういう風になっているんだと思うんですが、ひょんなことから、これはどうも XDG Base Directory という仕様によって決められているということを知りました。

仕様自体はここにあります。

この仕様上、設定ファイルの格納ディレクトリは、$XDG_CONFIG_HOME という環境変数に定義することになっていて、これが空か未定義の場合に $HOME/.config が使われることになっています。 たとえば peco の場合はこのあたりで実現されてる。

設定ファイルについては、これ以外にも XDG_CONFIG_DIRS という環境変数が規定されていて、これは設定ファイルが格納されているかもしれない複数のディレクトリを、以下のようなかんじで指定できることになっています。もちろん、先頭に定義されているディレクトリの方が優先されます。

export XDG_DATA_DIRS=$HOME/.devconfig:$HOME/.commonconfig

設定ファイルだけでなく、データの配置場所(XDG_DATA_DIR)とか、キャッシュの配置場所(XDG_CACHE_DIR) とか、ソケットやパイプの配置場所(XDG_RUNTIME_DIR。生存期間の規定まである) とかが定義されているので、ご興味あればご参照ください。

Inoreader でできるだけ早くフィード消化を行いたい

LDR がサービスを終了して早5ヶ月が経ちます。 ぼくはそれからもずっと情報収集を RSS に頼っていて、今は、だいたい 1,500 - 2,000 エントリ/day を 30 分くらいでざっと見ているかんじです。

RSS リーダとしては、LDR クローズ後は Inoreader を使用していて、ようやく自分としては納得いくフィード消化のスピードになってきたので、今日はそのへんのノウハウをまとめてみようと思います。

Inoreader

Inoreader は Google Reader や LDR と同様に、Web ベースの RSS リーダです。2013 年にリリースされていて、そこそこ枯れてる。 もちろん、公式のネイティブアプリもでています。

Inoreader - RSS&ニュースリーダー

Inoreader - RSS&ニュースリーダー

  • Innologica
  • ニュース
  • 無料

日本語化は相当進んでいて、UI にも不自然なところもでてきません。

前提

ぼくのフィード消化は

  • iOS 用ネイティブアプリの Inoreader でエントリを総なめ。気になったものを Pocket に登録。熟読については Pocket にて実施。

という内容にて実施しています。 なので、このエントリの前提は、「Inoreader に "エントリを熟読できる" ような UX は求めていない」ということになります。 (決して、Inoreader がそういう UX を実現できないということではありません)

また、基本的にはネイティブアプリを使ってほぼ全ての要望を満たせており、このエントリで書いていることもネイティブアプリ前提です。

フィード登録のノウハウ

フィードを消化する前に、フィードを Inoreader に登録するのを高速化したい。 これは、iOS のシェアシート経由で Inoreader を呼び出すことで可能になります。シェアシートにあることの何が素敵かというと、どのアプリを使っていたとしても、気になるエントリ、ページがあったら、それを即座に Inoreader で購読できることが大きい。

f:id:kiririmode:20180402161504p:plain:w300

シェアシート上の Inoreader の提供機能の一つが「Subscribe to feed」で、WebView で表示しているページをすぐにフィード登録することができます。

フィード消化のノウハウ

まず、RSS リーダでフィード消化をするにあたってその消化の速さに大きく効いてくるのは、如何にエントリを Inoreader にキャッシュさせるか、です。 できるだけ大量のエントリをキャッシュさせないと、都度ネットワークアクセスして、エントリの内容を Inoreader がフェッチして、それを表示するという待ちが生じることになります。 時間をかけずにエントリに目を通していくためには、この待ちを減らしていかないといけない。ぼくが気をつけているのは以下の点です。

  1. フィードをフォルダにグルーピングする
  2. 先読みするエントリ数を最大にする設定を行う

フィードをフォルダにグルーピングする

Inoreader のデフォルト状態だと、1 フィード内の(複数)エントリのみを読み込んで先読みする動作になります。 これでは、よほど 1 フィード内のエントリ数が多くないとぜんぜん先読みが効いてきません。Inoreader には、フォルダ内のフィード内エントリすべてを一度に閲覧できる機能(UI 上は「フォルダーを表示」) があるので、 これを利用することで、先読みをより効かせることができるようになります。

例えば、下記はおおよそ企業系テックブログを集めたフォルダになっていて、ここの「フォルダーを表示」すると、これらブログに未読エントリを一度に読み込むことができます。

f:id:kiririmode:20180402163619p:plain:w300

先読みするエントリ数を最大にする

いくつのエントリを読み込めるかは設定が可能で、最大が 100 エントリになります。 デフォルトだと結構絞られているので、これは 100 に設定しておけば良いです。

その他、フィード消化に便利な機能

最近感動したのが、個々のエントリを見ているときに「上」にスワイプすると、エントリ全文を読み込んでくれる機能がネイティブで実装されていること。 これって、かつて、LDR Full Feed で実現されていた機能です。懐かしい。

これを利用すると、以下のようなサマリだけしかないエントリの全文が読み込めたりするようになって、「熟読すべきエントリかどうか」をより正確に判断できるようになります。

f:id:kiririmode:20180402165331p:plain:w300

さらに、「上」にスワイプで全文フェッチであれば、「下」にスワイプはブラウザでの閲覧です。 起動するブラウザも、アプリ内 Web View なのか、外部ブラウザ (Chrome) なのかを選択できて、本当に至れり尽くせり。

まとめ

Inoreader けっこうフィード消化が捗るのでおすすめ。

マイクロサービスアーキテクチャ

マイクロサービスアーキテクチャ、ようやく読めました。あと何冊積読があるんだ…。

マイクロサービスアーキテクチャ

マイクロサービスアーキテクチャ

この本を読み終わって最初に思ったのは、この本は、研究における Survey 論文のようだな、ということです。 Survey 論文については、こちらの wikipedia:総説論文 が参考になりますが、一部引用すると、

総説論文ないしレビュー論文(ともにreview article)とは、ある主題における現行の理解の状態を要約した記事である。

ということです。

この本を読むだけで、マイクロサービスアーキテクチャが設計できる、実装できるようになるとか、そういう夢みたいなことは無いんですが、 マイクロサービスがどの方向にすすめるべきで、どういう点を考えなければならないのか、そしてその先行の技術にはどういうものがあるのかの概要が示される、そういう風に使う本なんだろうなと思っています。

この分野、かなり動きが激しくて、2016 年に発行されたこの本で取り上げられている各種 OSS についても、もう開発がストップしたものがいくつもあります。 その意味で、その知見・理解そのものをどんどん進化させていかなければいけないっていうのが、アーキテクチャと、アーキテクトそのものに求められているんだと思います。

第2章の「進化的アーキテクト」は、まさにそういうことを、チームビルディングやガバナンスの取り方などから記述していて、 技術的なところだけでなく、マネージメントなどの部分においても示唆されていて、すごく良い本でした。

私たちは何が起こるのかすべてを予測することはできないので、あらゆる不測の事態に備えて計画するのではなく、可能性が低いことを必要以上に指定したい衝動を避け、変化を許容するように計画すべきです。

iPad用のbluetoothキーボードがほしい

iPad 用の Bluetooth キーボードがほしいです。5,000 円くらいで良さげなものはないものでしょうか…。

ユースケース

iPad のソフトウェアキーボードで多くの文字を入力するのは人類には時期尚早なので、どうしてもキーボードが欲しくなります。 ぼくが iPad でたくさんの文字を打ちたいユースケースは以下の 2 つです。

  1. 家のリビングやソファー等、都合の良い場所で調べものをする (Mac を引きずり回したくない)
  2. 電車の中でメモを取る、エントリを書く

要件

自分の思考を整理するためにも、選ぶときに何が大事かということをまとめてみます。

  1. bluetooth 接続
    • iPad を遠くにおいて、手元でキーボードのみで操作みたいなことをしたい
    • 取り回しができるタブレットに、有線接続はねーだろ
  2. タブレットのカバーとは分離できること
    • iPad 本体のみを持ち回りたいというときはある
    • というわけで、タブレットのカバーとの「一体型」キーボードは不可
  3. 英語キーボード
  4. 薄い・軽い
    • 気軽に持ち回るためにも、バッグの中でスペースを取ってほしくない
    • 重いと持って行く気が失せるので、重くても 300 g くらいに抑えたい。
  5. バッテリー充電可。電池交換の運用はつらい。
  6. 安い.4,000 円くらいでまとめたい

逆に、気にしないのは以下のようなものです。

  1. キーボードとしての打鍵感
    • 昔からゴミのようなキーボードを使ってきてそういうのに慣れてる。HHKB とかでなくて良い

候補

logicool Keys-to-go

logicool の Keys-to-go。これめっちゃ良いです。

目を見張るのは、その薄さと軽さ。厚さは 1cm を切ってて、重さは 180 g。普通にキーピッチがあって(17 mm)、打鍵にも困らない。 素材がゴムっぽくて防水になっているのもポイントが高いです。

機能的には申し分ないんですが、ネックは価格。定価が 8,800 円で、メルカリやヤフオクでも、5,000 円程度を覚悟しなきゃいけません。

logicool K480

こちらも logicool。上部の凹み部分に、iPad を立てかけるタイプ。

これもすごく良くて、iPad だけでなくて iPhone とかも立てかけてキーボード入力できるようになるわけですが、 ネックはその重さで、815 g ある。ほとんど 1kg なわけで、キーボードだけでそれだけの重さがプラスされると日常的な持ち運びはちょっと面倒。

それと、電車で膝の上で入力することを考えると、このタイプの「立てかけ」は安定感に欠けていて、電車の揺れに伴って iPad が破損する未来が見えたので断念。

製品としても、据置きを意図したものだと思っています。

Anker ウルトラスリム Bluetooth ワイヤレスキーボード

今をときめく Anker が出している bluetooth キーボード。

重さが 200 g を切っていること、なにより Anker が出しているということで、できればこれを買いたいと思っていたんですが、ネックは駆動が単四電池であるということ。 いちおう 3 ヶ月以上持つらしいですんが、3 ヶ月ごとに電池を入れ替えることを想像すると、"うーん" と二の足を踏んでしまいます。 これがバッテリで駆動されているとしたらほぼ間違いなく買っていた…。

FUNDA キーボードケース

あんまり知らないブランドなんだけど、結構良さそうです。キーボード着脱式。

ケース付き、キーボードつきで、4.54 g。4.54 g ????!!!??!?

1 円玉 5 枚より軽い。 たぶん、454 g の間違いだと思うんですけど、カバー付きで 454 g であれば十分に軽い範囲です。(iPad のカバーはかなり軽くても 200 g とかです)。

キーボード着脱式の何が良いかというと、膝の上で、まさにラップトップとして使用する場合の安定感だと思います。 iPad が(たぶん)グラグラすることなくキーボード入力ができるのは良い。電車とかでパッと取り出して、パっと使える。 デザインも悪くない。

買ったヤツ

重量を確認することなく買ってしまいました。キーボード着脱式で、重さは 871 g。

重くね???????

キャンセル可能な時間を超えた後で重量に気付いてしまい、とりあえずこれを使ってみようかと思っております。