DHCPでIPアドレスとかは設定するけどresolv.confはいぢられたくない場合

DHCPIPアドレスとかは設定するけど /ec/resolv.conf は生成・変更されたくない場合のメモ。

Ubuntu

以下、14.04と15.04で確認。

手短に言うと、

  • /etc/resolv.conf をsymlinkではなく実ファイルにした上で、
  • 以下の内容で /etc/dhcp/dhclient-enter-hooks.d/disable-make_resolv_conf を作る
make_resolv_conf() { :; }

細かく書くと、こんな感じになってる。

  • dhclientが/sbin/dhclient-scriptを折りにふれ実行する
  • dhclient-script は resolv.conf を生成するためにシェル関数 make_resolv_conf を呼ぶ
  • dhclient-script にはフックポイントがあり、以下の場所にあるファイルを . (source) でロードしてくれる。つまり、ここで同名で シェル関数 make_resolv_conf を上書き定義すれば、resolv.conf 生成の処理を制御できる
    • /etc/dhcp/dhclient-enter-hooks
    • /etc/dhcp/dhclient-enter-hooks.d/*
    • /etc/dhcp/dhclient-exit-hooks
    • /etc/dhcp/dhclient-exit-hooks.d/*
    • ロードのタイミング (enter/exit) の説明は割愛
    • ディレクトリの方は、run-parts --list で列挙されたのがロードされるので、ファイル名の規則とかは run-parts(8) を参照
  • resolvconf パッケージがインストールされていると /etc/dhcp/dhclient-enter-hooks.d/resolvconf というファイルが存在するはず
  • /etc/dhcp/dhclient-enter-hooks.d/resolvconf は、デフォルトの make_resolv_conf を上書いて /sbin/resolvconf を使って /etc/resolv.conf を生成するようにしている
    • ただし、/etc/resolv.conf が symlink ではない場合は make_resolv_conf の上書きはしない

CentOS 6.5

NetworkManagerを無効化した上で、ifcfg-IF に PEERDNS=no を書く。

chkconfig NetworkManager off
service NetworkManager stop

vi /etc/sysconfig/network-scripts/ifcfg-eth0
# PEERDNS=no と書く

CentOSでもh2oでOCSP Staplingしたい

h2o/1.5.2です。

(たいていそうだと思いますが)中間CAから発行されたサーバー証明書の場合は、h2oではcertificate-fileで指定するファイルの内容を

  1. サーバー証明書
  2. 中間CAの証明書

という順序でcatで結合したものにします。順序があるので注意してください。参考: スマートフォン等、携帯端末の一部のみ「証明書が信頼できない」と警告が表示されます | Symantec

さて、h2oは起動時にOCSPレスポンダからレスポンスを得て、それのverifyが成功すればOCSP Staplingを有効にします。

具体的には、share/h2o/fetch-ocsp-response の中でopensslコマンドを使ってこの様にしています。

# certificate-fileで指定されたサーバー証明書からOCSPレスポンダのURIを得る
openssl x509 -in $certificate_file -noout -ocsp_uri

# certificate-fileから中間CAの証明書を得て($intermediate_file)、OCSPレスポンダにリクエストしてレスポンスを保存する
openssl ocsp -issuer $intermediate_file -cert $certificate_file -url $ocsp_uri -header Host $ocsp_host -noverify -respout resp.der

# OCSPレスポンスのverify。3つのうちどれかが通ればよい。
# 「for comodo」らしい
openssl ocsp -respin resp.der -VAfile $intermediate_file
# OpenSSL >= 1.0.2 で実装されたオプションを使用
openssl ocsp -respin resp.der -partial_chain -trusted_first -CAfile $intermediate_file
# OpenSSL <= 1.0.1 だとこれになる
openssl ocsp -respin resp.der -CAfile $intermediate_file

CentOS 6のOpenSSLは1.0.1なので、最後の3つめの方法でverifyすることになります。


さてさて、opensslはローカルにあるファイルからroot CA群の証明書(trust anchors)を得るのですが、オプションによって探す場所が変わります。

  • -CAfile が指定されていない場合
    • /etc/pki/tls/cert.pem (実体は /etc/pki/tls/certs/ca-bundle.crt で、多数の証明書が1ファイルに結合された内容)
    • /etc/pki/tls/certs/
  • -CAfile が指定されている場合


先ほどCentOS 6の場合は

openssl ocsp -respin resp.der -CAfile $intermediate_file

でverifyすると書きました。

resp.derのissuerは中間CAです。中間CAの証明書は-CAfileで指定しているのでパスします。
が、中間CA証明書のissuer (=root CA) の証明書は見つけることができないので、エラーとなってしまいます。これがCentOS 6でOCSP Staplingが有効にならない原因です。

ちなみに、-CAfileで指定するファイルの内容を、中間CA+root CAの証明書、にすればパスします。

ちなみにちなみに、Ubuntu 14.04では(パスが /usr/lib/ssl/certs/になりますが)root CA証明書へのhash linkがあるのでパスします。


で、CentOS 6でOCSP Staplingを有効にするにはどうすればいいか?なのですが、自分のサーバー証明書のroot CAの証明書を別途入手して、どこかに置いて /etc/pki/tls/certs/ の下にhash linkを作るのがいいのではないかと思います。

オマケ

hash linkを作るコマンドライン(ln)を吐く便利関数

cert_hashlink() {
  [ -r $1 ] || { echo "cannot read file: $1"; return 1; }
  echo "ln -snf $1 $(openssl x509 -noout -hash < $1).0"
}

h2oでmrubyを有効にしてビルドするにはruby >= 1.9が必要

h2o/1.5.2です。

h2oでmrubyを有効にするには、

cmake -DWITH_MRUBY=ON .

とすればよいのですが、ビルドを完遂するにはruby >= 1.9が必要です。

なぜなら、deps/mruby-onig-regexp/mrbgem.rake にこういう処理

    def run_command(env, command)
      STDOUT.sync = true
      Open3.popen2e(env, command) do |stdin, stdout, thread|
        print stdout.read
        fail "#{command} failed" if thread.value != 0
      end
    end

があるのですが、Open3#popen2e が使えるのは 1.9 からだからです。

マルチスレッドでgetaddrinfo(3)するとたまに Temporary failure in name resolution (EAI_AGAIN) で失敗する件

マルチスレッドでgetaddrinfo(3)するとたまにTemporary failure in name resolution (EAI_AGAIN) で失敗します。

自分は↓な環境で確認しました。

こういうコードで再現します。(ruby 2.1.4で確認)

なお、便宜的にrubyで再現コードを書いていますが、該当環境であれば言語問わずマルチスレッド+getaddrinfo(3)の組み合わせで発現する問題です。



対処法は、

rpmCentOS 6 Updatesにあるバージョン 2.12-1.149.el6_6.9 のに上げればOKです。


ちなみに自分は、Chef実行時にたまにberksのAPIサーバーの名前が引けなくてコケてたのがこれで直りました。

h2o試してみました、もしくはとりあえずサクッと既存のサイトをHTTP/2化する方法

先日、HTTP/2が正式な仕様として承認されると同時に、その実装であるH2Oのv1.0.0もリリースされました。

HTTP/2の情報はちょいちょいウォッチはしていたのですが、今までHTTP/2なサーバーを動かしたことはなく、いい機会なので自分のサイトをH2Oを使ってHTTP/2対応してみました。



大したことはやってないのですが、Apacheでサービスしており、認証やアクセス制限、ごにょごにょ黒魔術、CGI(!)やSSI(!!)なども動いているので、ApacheをH2Oにリプレースするのは無理でした。

そこで、H2Oをリバプーとして前段に置き、Apacheを後段に置く構成にしました。

設定ファイルはこんな感じです。

# -*- mode: yaml; -*-

user: www-data

max-connections: 1024
num-threads: 4

listen:
  host: 10.6.25.29
  port: 80
listen:
  host: 10.6.25.29
  port: 443
  ssl:
    certificate-file: /usr/irori/etc/ssl/cert_web/irori.org-cert.pem
    key-file: /usr/irori/etc/ssl/priv_web/irori.org-priv.pem
    cipher-suite: AES128-SHA:ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:HIGH:MEDIUM:!aNULL:!MD5:!RC4

hosts:
  "*":
    access-log: /dev/stdout
    paths:
      /:
        proxy.reverse.url: http://10.6.25.29:9080
        proxy.preserve-host: ON

"www.irori.org"のグローバルIPアドレスの80と443宛てのはルーターで10.6.25.29にNATされるので、それをlistenします。

443の方は、SSLの設定をします。

証明書は StartSSL で無料で発行したもらったものです。

certificate-file には発行されたサーバー証明書を指定しますが、中間CAの証明書も必要な場合は、cat でくっつけたファイルを指定します。

くっつける際には、くっつける順序に注意してください。最初にサーバー証明書、つづいて中間CA証明書やクロスルート証明書という風に、信頼チェーンの順序にしましょう。


今回はまるっとApacheに投げたいので、hostsは1つだけです。

H2Oは、リクエストのHostヘッダに合致するものがなければ最初の項目の設定に従うので、便宜的に「"*"」として、そこでぜんぶひっかけてApacheに丸投げしています。


以上、HTTP/0.9ってのもあったよね〜〜と感慨にふけっている老害がお送りしました。

『Serverspec』を読みました

『Serverspec』を読んだ(ご恵贈ありがとうございました!)ので感想とかを書いてみたいと思います。

ちなみに、本書は1/17発売なので既に書店に並んでいると思いますし、

Serverspec

Serverspec

電子書籍版もあるのでお急ぎの向きはそちらを購入するのもいいかと思います。

Serverspec, Specinfra を拡張したい、内部を知りたい、使い倒したい人

Serverespec, Specinfraの生みの親であり本書の著者でもある mizzy ([twitter:@gosukenator]) さんが自ら、

その代わり、開発に至る経緯や開発する上での哲学、動作仕様や内部のアーキテクチャソースコードレベルでの拡張方法など、表から見ただけではわからないようなことをふんだんに盛り込んでいます。


Serverspec について詳しく解説するということは、必然的に Specinfra のことも詳しく解説することになるわけですが、ドキュメントがまったくない Specinfra についても、ソースコードレベルで詳しく解説していますので、Specinfra をベースに Serverspec や Itamae とは違う何かをつくってみたい、という方にもお勧めです。

http://mizzy.org/blog/2014/12/25/1/

と言ってるのでまちがいないです。さっさと購入して熟読しましょう。

自分も以前、ある機能を追加しようと思いざっとコード読んだのですが、ちょっと把握するのに時間がかかりそうだったんで mizzy さんに相談したら実装してくれた経験があるのですが、本書を読んだ今なら自分でできそうな気がしています!!

Serverspecを使えればいい、またはServerspecには特に関心がないインフラエンジニアの人

使うだけならネットに情報が豊富にあるのでググれば事足ります。本書でもそういった「使い方」の情報は最小限にとどめているので、そういった目的では本書はおすすめできません。


そういった目的では」です。


「Infrastructure as Code」とは何か?改めて説明するまでもないと思いますが、インフラエンジニアならもうこの潮流は避けて通れないでしょう。

ServerspecはこのInfrastructure as Codeの重要なピースの一つである「テスト」を担う実装であり、本書の第一章にはServerspecの生まれた経緯や必要性、利用目的が書かれています。こういった内容は、コードを読んだだけでは伝わらないのでとても貴重だと思います。

つまり、本書を読むことでInfrastructure as Codeにおけるテストの重要性や戦略をServerspecというプロダクトを媒介として知ることができます。たとえServerspecに興味がなかったとしても、Infrastructure as Codeを知る上でこれはとても有益であるでしょう。

まとめると

すべてのインフラエンジニアにおすすめです!!!

MySQL 5.5以降のutf8mb4とPerlのDBD::mysqlのmysql_enable_utf8のワナ

結論から言うと、ひろせが望ましいと思う順にこうすればいいんじゃないの?ってのを列挙します。

  1. my.cnfに[libmysqlclient]グループを追加しそこにdefault-character-set = utf8mb4と書き、DBD::mysqlでは mysql_read_default_file=/etc/my.cnf;mysql_read_default_group=libmysqlclient と指定する
    • mysql_enable_utf8を使いたい場合はSET NAMES指定も必要
  2. my.cnfの[client]グループにdefault-character-set = utf8mb4と書き、DBD::mysqlでは mysql_read_default_file=/etc/my.cnf と指定する。mysqlbinlogなど--default-character-setオプションを解せずエラーになるものは--no-defaultsをつけて実行する
    • mysql_enable_utf8を使いたい場合はSET NAMES指定も必要
  3. DBD::mysqlで接続後に SET NAMES utf8mb4 を実行する

オハマリポイントは

  • DBD::mysql (libmysqlclientなクライアント全般?) は loose-default-character-set の設定値を認識しない
  • select結果をPerlの内部表現文字列(UTF8フラグ)にしようと DBD::mysqlmysql_enable_utf8 を使うと、ついでにクライアント側も文字コード設定を(utf8mb4ではなく)utf8にしてしまう

です。

DBD::mysql (libmysqlclientなクライアント全般?) は loose-default-character-set の設定値を認識しない

my.cnfの[client]グループに書いた設定はクライアント全般に適用されるので、ここにdefault-character-set = utf8mb4 と書ければいいのですが、[client]グループに書くとmysqlbinlogなどこのオプションに対応していないクライアントがエラー終了してしまいます。

my.cnfの設定項目には loose- というprefixをつけることができて、対応していないクライアントはそれを無視することができます。

なので、[client]グループに loose-default-character-set = utf8mb4 と書いておけば、シアワセになれると思っていたのですが、loose- だとDBD::mysql (libmysqlcientを使うクライアント全般かもしれません)が認識しくれず、libmysqlclientのデフォルト文字コード(特に変えてなければ latin1)になってしまいます(ガーン)。

仕方ないので、[libmysqlclient]といったグループを新規で追加してそこにdefault-character-setを書き、DBD::mysqlにはそのグループを読むように指示するか、mysqlbinlogがエラーになるのは --no-defaults オプションをつけて回避するか、の2択になります。

SET NAMESよりdefault-character-setを推す理由は、エスケープ処理に関連するからです。詳しくはこのへんを参照してください。

select結果をPerlの内部表現文字列(UTF8フラグ)にしようと DBD::mysqlmysql_enable_utf8 を使うと、ついでにクライアント側も文字コード設定を(utf8mb4ではなく)utf8にしてしまう

の通りですが、DBD::mysqlmysql_enable_utf8 はPerlの文字列の内部表現化(sv_utf8_decode)するだけでなく、mysql_options(MYSQL_SET_CHARSET_NAME) も実行します。

  • mysql_enable_utf8 が 1
  • mysql_enable_utf8 が 0
  • mysql_enable_utf8を指定しない
    • mysql_options(MYSQL_SET_CHARSET_NAME) は呼ばない

なにが問題になるかというと、mysql_enable_utf8 を 1 にしてネコ (Unicode: U+1F639, UTF-8: \xF0\x9F\x98\xB9) をutf8mb4なテーブルにinsertしても、utf8mb4でなく MYSQL_SET_CHARSET_NAME=utf8 になっているので DBD::mysql::st execute failed: Incorrect string value というエラーでinsertできません。

また、mysql_enable_utf8 を 0 にした場合(MYSQL_SET_CHARSET_NAME=latin1 を実行している)やmysql_enable_utf8を指定しなかった場合(libmysqlclientのデフォルトであるlatin1が使われる)、my.cnfの適切なグループにdefault-character-set = utf8mb4と書いてあれば、character_set_clientや_connectionはutf8mb4になりますが、libmysqlclientのエスケープ処理は(多分)latin1で行われるのでもしかしたら問題があるかもしれません。

エスケープ処理が本当にlatin1で行われるのかと、その場合具体的にどういう問題があるのか、はちょっと調べきれてないので情報あったら教えてください><)

エスケープの問題を別にすれば、mysql_enable_utf8を有効にしてもしなくてもいずれにせよ、MySQLに接続した直後にSET NAMES utf8mb4を実行すれば、ネコもちゃんとハンドリングできます。

生のDBD::mysqlなら、Callbacksのconnectedでやるといいと思います。

my $dbh = DBI->connect($dsn, $user, $password, {
    ...
    Callbacks => {
        connected => sub {
            $_[0]->do('SET NAMES utf8mb4');
            return;
        },
    },
});