<mazgi.github.io 移行済>ssmjp で Gentoo の話をしてきました

移行しました=> https://mazgi.github.io/posts/2015.10/gentoo-talk-on-ssmjp/


先週の話ですがssmjp 2015/09の回におじゃましてきました。

「一緒にプロダクション環境でのGentoo勉強会」やりましょう!という話を現実にしてくださった@usaturnさん、そして初参加&&初登壇&&Gentooネタという暴挙(?)を広い懐で受け入れてくださった #ssmjp の皆様、本当にありがとうございました!

内容とか

以前会社のblogに書いた「自社サービスをGentoo Linuxのパッケージ形式であるebuildでパッケージングしてdeployする」という話をベースになぜそのような選択に至ったかなどをお話させていただきました。

techlog.mvrck.co.jp

当日のスライドはこちらにアップロードしております。

www.slideshare.net

オフィシャルblogでのGentooカミングアウトに留まらず発表の場までいただきありがとうございました!
おかげさまで無事フラグを回収できました。

普通のScalaアプリケーションをebuildでdeployする

会社ではほとんどのアプリケーションをScalaで開発しさくらのクラウドやハウジング環境で運用しているのですが、どんな言語で開発するにせよどのような環境で運用するにせよ、当然ながらアプリケーションはdeployする必要があります。

deployに関しては色々な方法があると思うのですが、私は「ミドルウェアのインストールやアップデートと同じように自社プロダクトもパッケージとしてdeployしたい」と考えていました。
つまりnginxやMariaDBをアップデートするように yum updateapt-get upgrade , emerge -uND world (「うどんアップデート」というらしいですね)すると自社プロダクトもアップデートされるということです。

そう!emerge !!
portageパッケージ管理システムに則ってアプリのebuildを作ればいいではないですか!!!
1日に何回も何十回もcommitがpushされる自社プロダクトで、もし都度バイナリパッケージを作るとなるとパッケージ作成自体のコストが高く感じてしまいますが、ebuildなら「GitHubのリポジトリから直接masterのHEADを取得後コンパイルしインストールする」という手順自体をパッケージングできます!
portageにはインストールすると同時にバイナリパッケージを作る機能もあるので、たとえScalaアプリのコンパイルに初回は1曲聴ける程度の時間がかかったとしても2回目以降はイントロを聴いている間にインストールできます!

ということでScalaアプリをebuildでdeployするようになってから数ヶ月経ち課題を残しつつも安定してきたので概要を書き留めておきます。

なお、世間にはGentoo Linuxでサービス運用している会社が少なくとも2社あるようです。

2000年以前はおそらくどこにも採用されていなかったことを考えると近年の導入会社数の伸び率は目覚ましく、ひとりのGentoo Linuxユーザーとしてもうれしい限りです。

流れ

例としてHelloPlayというPlayFrameworkを使ったScalaのサンプルアプリケーションをebuildでパッケージングしてみます。 インストール先はさくらのクラウドですが、今回プラットフォーム固有の機能は使っていないのでクラウドでもハウジングでも差異はありません。
また、バージョンとして下記2つを想定します。

  • 安定版である 0.0.1 → tag v0.0.1 がインストールされる
  • 開発版である 9999 → その時点のmasterのHEADがインストールされる

なおこのアプリケーションでは v0.0.1 に対して masterGET /hello というエンドポイントが追加されており、リクエストを受けるとステータス200で"Hello"というメッセージを返す機能が追加されています。
差分はこんな感じです。

f:id:mazgi:20150818223830p:plain

ebuild作成

このアプリケーションを www-apps/playscala-example というパッケージにします。
こんなebuildを書いてみました。
また、 application.conf の雛形や起動スクリプトなども作成します。

ebuild自体の差分としては開発版である 9999 には ~amd64 キーワードを指定しますが、安定版である 0.0.1 にはキーワードを指定しません
ebuild内でバージョンを判定することにより 0.0.1 ではtagを、 9999 ではmasterのHEADを取得するよう振る舞いを分けます。

if [[ ${PV} != 9999 ]]; then
    # set tag as "EGIT_COMMIT"
    EGIT_COMMIT="v${PV}"
    SLOT=$(get_version_component_range 1-2)
fi

リポジトリ登録

repositories.xmlを作成自身のportageリポジトリのrootに配置します。

内容はこのようなXMLファイルです。

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE repositories SYSTEM "/dtd/repositories.dtd">
<repositories xmlns="" version="1.0">
  <repo quality="experimental" status="unofficial">
    <name><![CDATA[mazgi-experimental]]></name>
    <description lang="en"><![CDATA[An overlay by @mazgi]]></description>
    <homepage>http://mazgi.com/</homepage>
    <owner type="person">
      <email>MATSUKI.Hidenori@gmail.com</email>
      <name><![CDATA[Hidenori MATSUKI]]></name>
    </owner>
    <source type="git">git://github.com/mazgi/portage-overlay.git</source>
  </repo>
</repositories>

このrepositories.xml/etc/portage/repos.confに今回はmazgi-experimental.confという名前で配置します。
内容はこんな感じです。

$ cat /etc/portage/repos.conf/mazgi-experimental.conf
[mazgi-experimental]
location = /var/lib/portage/repo.data/mazgi-experimental
sync-type = git
sync-uri = git://github.com/mazgi/portage-overlay.git
auto-sync = yes

またScalaアプリケーションをsbtでビルドするようebuildを書いたので、sbtが必要になりますが、sbtのebuildパッケージも作成して同じリポジトリに入れています。
普通にsbtのebuildとして使えますのでもし必要でしたら上記の設定を行って emerge sbt-bin してみてください。

インストール

まずは sync して先ほど設定したリポジトリを取得します。

$ sudo emaint sync -r mazgi-experimental
>>> Syncing repository 'mazgi-experimental' into '/var/lib/portage/repo.data/mazgi-experimental'...
/usr/bin/git pull
Already up-to-date.
=== Sync completed for mazgi-experimental

Action: sync for repo: mazgi-experimental, returned code = 0

またJDK周りのUSEフラグを必要に応じて変更しておきます。

$ sudo flaggie dev-java/oracle-jdk-bin -awt -fontconfig
$ sudo flaggie dev-java/icedtea-bin -X -alsa -cups

ここまでやって emerge playscala-example すると下記のようにJDKやsbt等必要なパッケージを含めて全てがリストアップされます。

$ sudo emerge -uav playscala-example

These are the packages that would be merged, in order:

Calculating dependencies... done!
[ebuild  N     ] media-libs/giflib-4.1.6-r3::gentoo  USE="-X -rle -static-libs" ABI_X86="(64) -32 (-x32)" 0 KiB
[ebuild  N     ] app-eselect/eselect-java-0.1.0::gentoo  0 KiB
[ebuild  N     ] dev-util/patchelf-0.8::gentoo  0 KiB
[ebuild  N     ] media-libs/libpng-1.6.16:0/16::gentoo  USE="-apng (-neon) -static-libs" ABI_X86="(64) -32 (-x32)" 0 KiB
[ebuild  N     ] media-libs/lcms-2.6-r1:2::gentoo  USE="threads zlib -doc -jpeg -static-libs {-test} -tiff" ABI_X86="(64) -32 (-x32)" 0 KiB
[ebuild  N     ] dev-java/java-config-wrapper-0.16::gentoo  0 KiB
[ebuild  N     ] sys-apps/baselayout-java-0.1.0::gentoo  0 KiB
[ebuild  N     ] dev-lang/nasm-2.11.08::gentoo  USE="-doc" 0 KiB
[ebuild  N     ] dev-java/java-config-2.2.0:2::gentoo  PYTHON_TARGETS="python2_7 python3_4 -python3_3" 0 KiB
[ebuild  N     ] gnome-base/gsettings-desktop-schemas-3.14.2::gentoo  USE="-introspection" 0 KiB
[ebuild  N     ] media-libs/libjpeg-turbo-1.3.1::gentoo  USE="-java -static-libs" ABI_X86="(64) -32 (-x32)" 0 KiB
[ebuild  N     ] virtual/jpeg-62:62::gentoo  ABI_X86="(64) -32 (-x32)" 0 KiB
[ebuild  N f   ] dev-java/oracle-jdk-bin-1.8.0.51:1.8::gentoo  USE="-alsa -awt -cups -derby -doc -examples -fontconfig -javafx -jce -nsplugin -pax_kernel (-selinux) -source" 0 KiB
[ebuild  N    ~] virtual/jdk-1.8.0:1.8::gentoo  0 KiB
[ebuild  N     ] dev-java/icedtea-bin-7.2.5.5:7::gentoo  USE="-X -alsa -cjk -cups -doc -examples -nsplugin -pulseaudio (-selinux) -source -webstart" 0 KiB
[ebuild  N     ] virtual/jdk-1.7.0:1.7::gentoo  0 KiB
[ebuild  N    ~] virtual/jre-1.8.0:1.8::gentoo  0 KiB
[ebuild  N     ] virtual/jre-1.7.0:1.7::gentoo  0 KiB
[ebuild  N     ] dev-java/sbt-bin-0.13.7:0.13::mazgi-experimental  0 KiB
[ebuild  N     ] www-apps/playscala-example-0.0.1:0.0::mazgi-experimental  USE="autoclean symlink -doc" 0 KiB

Total: 20 packages (20 new), Size of downloads: 0 KiB
Fetch Restriction: 1 package

The following keyword changes are necessary to proceed:
 (see "package.accept_keywords" in the portage(5) man page for more details)
# required by www-apps/playscala-example-0.0.1::mazgi-experimental
# required by playscala-example (argument)
=virtual/jdk-1.8.0 ~amd64
# required by www-apps/playscala-example-0.0.1::mazgi-experimental
# required by playscala-example (argument)
=virtual/jre-1.8.0 ~amd64

The following license changes are necessary to proceed:
 (see "package.license" in the portage(5) man page for more details)
# required by virtual/jdk-1.8.0::gentoo
# required by virtual/jre-1.8.0::gentoo
# required by www-apps/playscala-example-0.0.1::mazgi-experimental
# required by playscala-example (argument)
>=dev-java/oracle-jdk-bin-1.8.0.51 Oracle-BCLA-JavaSE

Would you like to add these changes to your config files? [Yes/No] 

JDK 1.8 のmaskを外し、Oracle JDKのライセンスに同意し、インストールを進めます。

$ sudo dispatch-conf
$ sudo emerge -uav playscala-example

These are the packages that would be merged, in order:

Calculating dependencies... done!
[ebuild  N     ] app-eselect/eselect-java-0.1.0::gentoo  0 KiB
[ebuild  N     ] dev-java/java-config-wrapper-0.16::gentoo  0 KiB
[ebuild  N     ] sys-apps/baselayout-java-0.1.0::gentoo  0 KiB
[ebuild  N     ] dev-java/java-config-2.2.0:2::gentoo  PYTHON_TARGETS="python2_7 python3_4 -python3_3" 0 KiB
[ebuild  N f   ] dev-java/oracle-jdk-bin-1.8.0.51:1.8::gentoo  USE="-alsa -awt -cups -derby -doc -examples -fontconfig -javafx -jce -nsplugin -pax_kernel (-selinux) -source" 0 KiB
[ebuild  N    ~] virtual/jdk-1.8.0:1.8::gentoo  0 KiB
[ebuild  N    ~] virtual/jre-1.8.0:1.8::gentoo  0 KiB
[ebuild  N     ] dev-java/sbt-bin-0.13.7:0.13::mazgi-experimental  0 KiB
[ebuild  N     ] www-apps/playscala-example-0.0.1:0.0::mazgi-experimental  USE="autoclean symlink -doc" 0 KiB

Total: 9 packages (9 new), Size of downloads: 0 KiB
Fetch Restriction: 1 package

Would you like to merge these packages? [Yes/No] 

インストールできたら application.conf を作成し、startしてみます。

$ sudo cp /etc/playscala-example/application.conf{.example,}
$ sudo /etc/init.d/playscala-example start
 * Starging playscala-example ...                                                     [ ok ]
$ /etc/init.d/playscala-example status
 * status: started
$ curl -L 'localhost:9000/' -o /dev/null -w '%{http_code}\n' -s
200

無事 / へのGETが200を返すことを確認できました!

アップデート

安定版である 0.0.1 がインストールできました。

$ curl -L 'localhost:9000/hello' -o /dev/null -w '%{http_code}\n' -s
404

しかしながら 0.0.1/hello へのGETが404になってしまいます。 そこで、今度は開発版の 9999 にアップデートしてみます。

9999 は特定のtagを参照しているわけではなくemergeした時点でのmasterのHEADがインストールされます。

$ sudo emerge -uavq \=www-apps/playscala-example-9999
[ebuild  NS   ] www-apps/playscala-example-9999 [0.0.1] USE="autoclean -doc symlink" 

The following keyword changes are necessary to proceed:
 (see "package.accept_keywords" in the portage(5) man page for more details)
# required by =www-apps/playscala-example-9999 (argument)
=www-apps/playscala-example-9999 ~amd64

Would you like to add these changes to your config files? [Yes/No] 

Autounmask changes successfully written.

 * IMPORTANT: config file '/etc/portage/package.accept_keywords/00_default' needs updating.
 * See the CONFIGURATION FILES section of the emerge
 * man page to learn how to update config files.

~amd64 でマスクされていますので外します。

$ sudo dispatch-conf

マスクを外したら改めてemergeします。

$ USE="-symlink" sudo emerge -uavq \=www-apps/playscala-example-9999
[ebuild  NS   ] www-apps/playscala-example-9999 [0.0.1] USE="autoclean -doc -symlink" 

Would you like to merge these packages? [Yes/No] 

このパッケージは /var/lib/playscala-example/versions 以下にバージョン毎にsymlinkを作る構造になっているのでsymlinkを貼り替えます。

$ cd /var/lib/playscala-example/versions
/var/lib/playscala-example/versions ~
$ sudo rm current
$ sudo ln -s playscala-example.9999.957b03072271df6586c1e6db8b2b76b4be5e3f08 current
$ sudo chown play:play -h current
$ ls -l
total 4
lrwxrwxrwx 1 play play  63 Aug 31 16:19 current -> playscala-example.9999.957b03072271df6586c1e6db8b2b76b4be5e3f08/
drwxr-xr-x 1 play play 100 Aug 31 03:10 playscala-example.0.0.1.9e023916c4c911408145ff5f181a42543fbced4a/
drwxr-xr-x 1 play play 102 Aug 31 16:12 playscala-example.9999.957b03072271df6586c1e6db8b2b76b4be5e3f08/

サービスを再起動します。

$ sudo /etc/init.d/playscala-example restart
 * Stopping playscala-example ...                                                     [ ok ]
 * Starging playscala-example ...                                                      [ ok ]
$ sudo /etc/init.d/playscala-example status
 * status: started

/hello へのGETが通るようになったか確認してみます。

$ curl -L 'localhost:9000/' -o /dev/null -w '%{http_code}\n' -s
200
$ curl -L 'localhost:9000/hello' -w '\n%{http_code}\n'
Hello
200

無事アップデートされ /hello へのGETが200を返すことが確認できました!

まとめのかわりに

以上、さらっと普通のScalaアプリケーションをebuildでdeployする方法を書いてみたのですが、これ、「よーし!当社もGentooにしたしちょっとebuild作成に挑戦するぞ!」っていう方にとっては暗黙知が多すぎて伝わらないですね。

自分でフラグ立ててしまったので回収しつつ、もう少し順を追って書いてみようと思います。

まずは自社サービスのebuild化とdeployについて会社のブログに書くところからかな...!

さくらのクラウドで普通のゲートウェイサーバーを構築する

会社では相変わらずさくらのクラウド上で色々やっていますが、以前のGentooの記事が好評だったので今回も同じくGentooでいこうと思います!

今回は図のような構成でごく普通のGatewayサーバーを構築し、矢印で示したサーバーがインターネットに出られるようにします。

f:id:mazgi:20150814211433p:plain

まずはカーネルのコンフィグレーションですが、CONFIG_IP_NF_TARGET_MASQUERADEを有効にしておきます。

[root@gateway-left] # uname -a
Linux gateway-left 4.0.5-gentoo #1 SMP Fri Aug 14 20:27:22 JST 2015 x86_64 Intel(R) Xeon(R) CPU E5-2640 0 @ 2.50GHz GenuineIntel GNU/Linux
[root@gateway-left] # zgrep CONFIG_IP_NF_TARGET_MASQUERADE /proc/config.gz
CONFIG_IP_NF_TARGET_MASQUERADE=m

またsysctlnet.ipv4.ip_forward1に設定します。

[root@gateway-left] # sysctl -p
net.ipv4.ip_forward = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.rp_filter = 1

iptablesはこんな感じにしてみます。
なお、eth0がWAN側インタフェース、eth1がLAN側インタフェースです。

[root@gateway-left] # iptables -L -v
Chain INPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
  671  100K ACCEPT     all  --  any    any     anywhere             anywhere             ctstate RELATED,ESTABLISHED
    0     0 ACCEPT     all  --  lo     any     anywhere             anywhere
    0     0 ACCEPT     icmp --  any    any     anywhere             anywhere
    1    60 ACCEPT     tcp  --  any    any     anywhere             anywhere             tcp dpt:22 ctstate NEW limit: up to 2/min burst 3 mode srcip-dstip
    0     0 DROP       tcp  --  any    any     anywhere             anywhere             multiport dports epmap,netbios-ssn,microsoft-ds
    0     0 DROP       udp  --  any    any     anywhere             anywhere             multiport dports epmap,netbios-ns,netbios-dgm,microsoft-ds
   15   764 LOG_AND_DROP  all  --  any    any     anywhere             anywhere

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
   36 12342 ACCEPT     all  --  eth1   any     anywhere             anywhere
   31 21996 ACCEPT     all  --  eth0   any     anywhere             172.x.0.0/16

Chain OUTPUT (policy ACCEPT 506 packets, 66758 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 ACCEPT     all  --  any    lo      anywhere             anywhere
    0     0 LOG_AND_DROP  all  --  any    eth0    anywhere             255.255.255.255

Chain LOG_AND_DROP (2 references)
 pkts bytes target     prot opt in     out     source               destination
   15   764 LOG        all  --  any    any     anywhere             anywhere             limit: avg 1/sec burst 5 LOG level warning prefix "(IPTABLES_DROPPED)"
   15   764 DROP       all  --  any    any     anywhere             anywhere

natテーブルはこんな感じで。

[root@gateway-left] # iptables -L -v -t nat
Chain PREROUTING (policy ACCEPT 2155 packets, 139K bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain INPUT (policy ACCEPT 1 packets, 60 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 30 packets, 1931 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain POSTROUTING (policy ACCEPT 30 packets, 1931 bytes)
 pkts bytes target     prot opt in     out     source               destination
   11 10788 MASQUERADE  all  --  any    eth0    anywhere             anywhere

Gateway経由でインターネットにでる各サーバーの設定はこんな感じです。
172.x.y.zがGatewayのIPアドレスです。

$ cat /etc/conf.d/net.eth1
config_eth1="172.x.a.b/16"
routes_eth1="default via 172.x.y.z"
dns_servers_eth1="172.x.v.1 172.x.v.2"
dns_domain_eth1="example.com"

以上、お手軽ですね。

ところでさくらのクラウドにはVPCルーターというアプライアンスが用意されており、これを使うともっとお手軽にネットワークを構築することができます。

VPCルータ | さくらのクラウドニュース

RedmineのユーザーページにGitHubへのリンクを表示する

会社では開発がほぼGitHub.com+Slackで完結するようにしていて、これはなかなかうまくいっています(と思っています)。 ただ、ハードウェア障害の対応等GitHubに載せにくいタスクもあり、そのようなタスクの管理にはさくらのクラウド上にRedmineを構築して使っています。

このように「GitHubメイン、ときどきRedmine」という状況で、Redmine上に「このRedmineユーザーはこのGitHubユーザーです」って表示できないかなと思ったらカスタムフィールドを使ってあっさりできたのでメモしておきます。

表示としてはこのような感じになります。

f:id:mazgi:20150813165522p:plain

手順

カスタムフィールドの作成

なにはともあれカスタムフィールドを作ります。

f:id:mazgi:20150813165541p:plain

typeは Users にします。

f:id:mazgi:20150813165412p:plain

Name は"GitHub Account"にしました。
Link values to URL がキモでAdministrator Guideによると https://github.com/%value% のように %value% でカスタムフィールドの値を参照できるようです。

f:id:mazgi:20150813165358p:plain

保存するとカスタムフィールドが作成されます。

f:id:mazgi:20150813165556p:plain

Redmineユーザーの編集

つぎにRedmineの各ユーザーにGitHubアカウントを入力していきます。
ユーザー編集ページで先ほど作った"GitHub Account"が入力できるようになっていますね。

f:id:mazgi:20150813165430p:plain

保存するとこのようにGitHubアカウント名がリンクとして表示されます。

f:id:mazgi:20150813165522p:plain

クリックするとちゃんとGitHubのユーザーページに飛びます。

f:id:mazgi:20150813165337p:plain

めでたしめでたし。
これ、個人ページのURLにアカウント名が含まれるあらゆるサービスに使えますね。
せっかくなのでTwitterとかFacebookとか作ってみようと思います。

参考にさせていただいたページ

さくらのクラウドで普通のLDAP認証ができるサーバーを量産する

会社でSSHの公開鍵やSUDOの権限をLDAPで一元管理していっているのですが、忘れないうちにLDAPクライアント側の構築方法を書いてみます。
なお、サーバーはさくらのクラウド上で構築し、LDAPサーバーはldap1.example.comというFQDNでアクセスできるものとします。

手順

OSとパッケージ

クラウドなので基本となるインスタンスを1台構築し複製することにします。
複製したインスタンスは普通のサーバーとして使う想定ですのでディストリビューションにはGentoo Linuxを使用します。
最近構築したのでカーネルバージョンは4.0.5と新しめです。

インスタンスの複製はロードバランサとサーバクローンで簡単スケールアウトというTIPSにスクリーンショット付きで解説されています。

# uname -a
Linux base 4.0.5-gentoo #1 SMP Wed Jul 1 02:23:16 JST 2015 x86_64 Intel(R) Xeon(R) CPU E5-2640 0 @ 2.50GHz GenuineIntel GNU/Linux

SSSD経由でLDAP認証を行うために必要なパッケージをインストールします。 USEフラグ設定のポイントは以下のとおりです。

  • OpenLDAPはクライアントのみでよいためminimalを指定
  • SSH公開鍵は後述するsss_ssh_authorizedkeysで取得するためOpenSSHにはldap指定不要
  • SSSDにはsshsudoを指定
  • sudoにはldapを指定
    (これは今回SUDOの設定をSSSD経由ではなくLDAPから直接取得したためです)
# emerge -pvq openldap openssh sssd sudo
[ebuild   R   ] net-nds/openldap-2.4.38-r2  USE="berkdb crypt gnutls ipv6 minimal sasl ssl syslog tcpd -cxx -debug -experimental -icu -iodbc -kerberos -odbc -overlays -perl -samba (-selinux) -slp -smbkrb5passwd" ABI_X86="(64) -32 (-x32)" 
[ebuild   R   ] net-misc/openssh-6.9_p1-r2  USE="hpn pam pie ssl -X -X509 -bindist -debug -kerberos -ldap -ldns -libedit -sctp (-selinux) -skey -ssh1 -static" 
[ebuild   R   ] sys-auth/sssd-1.12.4  USE="manpages nls ssh sudo -acl -augeas -autofs -locator -netlink -nfsv4 -python -samba (-selinux) {-test}" ABI_X86="(64) -32 (-x32)" PYTHON_SINGLE_TARGET="python2_7 -python3_3 -python3_4" PYTHON_TARGETS="python2_7 python3_3 -python3_4" 
[ebuild   R   ] app-admin/sudo-1.8.12  USE="ldap nls pam sendmail -offensive (-selinux) -skey" 

時代はflaggieなのでflaggieでUSEフラグを設定するといいと思います。

LDAPクライアントの設定

パッケージをインストールしたらLDAPクライアントとして仕立てていきます。

LDAPクライアントとしての基本的な設定を/etc/openldap/ldap.confに書きます。

# grep -vE '^\s*($|#)' /etc/openldap/ldap.conf
BASE dc=example,dc=co,dc=jp
URI ldap://ldap1.example.co.jp ldap://ldap2.example.co.jp
tls_reqcert naver
sudoers_base ou=SUDOers,dc=example,dc=co,dc=jp
nss_initgroups backlink
binddn cn=Authenticator,dc=example,dc=co,dc=jp
bindpw P@ssw0rd!

sudoコマンドが読む/etc/ldap.conf.sudoファイルはldap.confへのsymlinkにしています。 なお、sudoがどのldap.confを読んでいるかはリンク先の方法で調べることができます。

sudoがどのldap.confを読んでいるか確認する - Qiita

# ls -l /etc/openldap/ldap.conf /etc/ldap.conf.sudo
lrwxrwxrwx 1 root root  18 Jul 19 16:35 /etc/ldap.conf.sudo -> openldap/ldap.conf
-rw-r--r-- 1 root root 250 Jul 19 16:36 /etc/openldap/ldap.conf

SSSDの設定

続いてSSSDの設定を行います。 ドメインはexampleとしています。

# grep -vE '^\s*($|#)' /etc/sssd/sssd.conf
[sssd]
config_file_version = 2
services = nss,pam,sudo,ssh
domains = example
debug_level = 1
[nss]
filter_users = root,ldap,named,avahi,haldaemon,dbus,radiusd,news,nscd
[pam]
[sudo]
subdomain_enumerate = true
debug_level = 9
[domain/example]
id_provider = ldap
auth_provider = ldap
sudo_provider = ldap
ldap_search_base = dc=example,dc=co,dc=jp
ldap_sudo_search_base = ou=SUDOers,dc=example,dc=co,dc=jp
ldap_tls_reqcert = never
ldap_uri = ldap://ldap1.example.co.jp
ldap_schema = rfc2307
debug_level = 1
enumerate = true
ldap_default_bind_dn = cn=Authenticator,dc=example,dc=co,dc=jp
ldap_default_authtok = P@ssw0rd!
ldap_group_object_class = posixGroup
ldap_group_search_base = ou=Group,dc=example,dc=co,dc=jp
ldap_group_name = cn
ldap_group_member = memberUid
ldap_id_use_start_tls = false
chpass_provider = ldap
cache_credentials = true

Name Service Switchの設定

passwd,shadow,groupsssを参照するようnsswitch.confを設定します。
sudoersldapsss両方を参照するよう設定していますが、これは私がうまくSSSD経由でSUDOersを取得できなかったためこのようになっています。。

# grep -vE '^\s*($|#)' /etc/nsswitch.conf
passwd:      compat sss
shadow:      compat sss
group:       compat sss
hosts:       files dns
networks:    files dns
services:    db files
protocols:   db files
rpc:         db files
ethers:      db files
netmasks:    files
netgroup:    files
bootparams:  files
automount:   files
aliases:     files
sudoers:     files ldap sss

PAMの設定

ディストリビューションによってデフォルトの設定がやや異なりますが、今回はこのように設定しました。
おおむねpam_sss.soを使用している行が追加した行です。合わせてpam_unix.soを使用している行をrequiredからsufficientに変えています。
またログイン時にホームディレクトリが作成されるようにpam_mkhomedir.soを使用しています。なお、/homeはNFSで共有されておりどのサーバーに入っても同じホームディレクトリが見えるようにしています。

# grep -vE '^\s*($|#)' /etc/pam.d/system-auth
auth            required        pam_env.so 
auth            sufficient      pam_unix.so try_first_pass likeauth nullok 
auth            sufficient      pam_sss.so use_first_pass
auth            optional        pam_permit.so
account required        pam_unix.so 
account [default=bad success=ok user_unknown=ignore]            pam_sss.so
account optional        pam_permit.so
password        required        pam_cracklib.so difok=2 minlen=8 dcredit=2 ocredit=2 retry=3 
password        sufficient      pam_unix.so try_first_pass use_authtok nullok sha512 shadow 
password        sufficient      pam_sss.so use_authtok
password        optional        pam_permit.so
session         required        pam_limits.so 
session         required        pam_env.so 
session         required        pam_unix.so 
session         optional        pam_mkhomedir.so skel=/etc/skel/ umask=0077
session         optional        pam_sss.so
session         optional        pam_permit.so

OpenSSHの設定

OpenSSH 6.2からAuthorizedKeysCommandという項目名で公開鍵を取得する外部プログラムを呼び出せるようになっています。
この設定とSSSDが提供するsss_ssh_authorizedkeysコマンドによってLPKパッチを当てなくてもLDAPからSSSDを経由して公開鍵を取得できるようになります。
余談ですがLDAPサーバー側はLPKスキーマが欲しいのでOpenSSHのUSEフラグにldapを指定してインストールしています。

# grep -vE '^\s*($|#)' /etc/ssh/sshd_config
PubkeyAuthentication yes
AuthorizedKeysCommand /usr/bin/sss_ssh_authorizedkeys
AuthorizedKeysCommandUser nobody
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM yes
PrintMotd no
PrintLastLog no
UsePrivilegeSeparation sandbox          # Default for new installations.
Subsystem       sftp    /usr/lib64/misc/sftp-server
AcceptEnv LANG LC_*

このようにsss_ssh_authorizedkeysコマンドの引数にユーザー名を渡すと公開鍵が取得できることを確認できます。

[mazgi@base] $ sss_ssh_authorizedkeys mazgi
ssh-rsa AAAA********Hs2V mazgi@BRUICHLADDICH.local
ssh-rsa AAAA********61rn mazgi@Ardbeg.local

デーモンの起動

ここまで設定できたらsshdsssdを起動します。
この例ではホームディレクトリをマウントするためにnfsclientも起動しています。

# rc-status
Runlevel: default
 netmount                                                          [  started  ]
 local                                                             [  started  ]
Dynamic Runlevel: hotplugged
Dynamic Runlevel: needed
 rpcbind                                                           [  started  ]
 rpc.pipefs                                                        [  started  ]
 rpc.idmapd                                                        [  started  ]
 rpc.statd                                                         [  started  ]
 nfsclient                                                         [  started  ]
Dynamic Runlevel: manual
 net.eth0                                                          [  started  ]
 sshd                                                              [  started  ]
 sssd                                                              [  started  ]
 net.eth1                                                          [  started  ]

ここまでの手順でLDAPに登録されている公開鍵でSSHログインしてsudoできるようになります。

[mazgi@localhost] $ id
uid=10001(mazgi) gid=10000(example) groups=10000(example),11001(level1),11002(level2),11003(level3),11004(level4),11005(level5)
[mazgi@localhost] $ sudo -ll
Matching Defaults entries for mazgi on localhost:
    ignore_local_sudoers, insults, !authenticate, !env_reset

User mazgi may run the following commands on localhost:

LDAP Role: level1
    RunAsUsers: root
    Commands:
    /bin/false

LDAP Role: level2
    RunAsUsers: root
    Commands:
    /usr/bin/tail
    /usr/bin/tailf
    /usr/bin/less
    /bin/ls
    /usr/bin/sha1sum
    /usr/bin/sha224sum
    /usr/bin/sha256sum
(snip)

ちゃんとログインできてUIDとグループとSUDO情報が取得できていますね!

おわりに

会社ではこのインスタンスを複製して本番環境や社内サービスに使っています。
クラウドなので複製後は以下の3ステップでサーバーが使い始められます。楽チンですね!

  • IPアドレスの設定
  • ホスト名の設定
  • SSHホスト鍵の再生成

参考にさせていただいたページ

flaggieをはじめ数々のGentoo TIPSについてはEmacs ひきこもり生活を参考にさせていただいています。

SSSDの設定についてはsssd.conf ファイルの設定をはじめとしたRed Hat社が公開してくださっているRHELのドキュメントが大変参考になります。

さくらのクラウドでサーバーを複製する方法はさくらのクラウドニュースで紹介されているロードバランサとサーバクローンで簡単スケールアウトというTIPSが参考になります。

普通のうるう秒対応をしました(June 30, 2015)

ITシステムに関わる人にとっては気が気ではなかったであろううるう秒ですが、無事に過ごすことができましたのでやったことをメモしておきます。

www.nikkei.com

やったこと

今回のうるう秒を迎えるにあたっては自宅ラック友の会 ポータルの記事とHIROCASTERさんの7/1の閏秒を迎えるにあたってLinuxでは何をすべきか?を大いに参考にさせていただきました。
ありがとうございます。

行った対策は以下です。

  • うるう秒による影響範囲の調査
    • Linuxカーネルのバージョン確認
    • ntpdのバージョン確認とアップデート
  • (ベンダー様による)ネットワーク機器の対策
  • Slewモードで動くNTPサーバーの構築
  • サーバーのNTP設定変更
  • 当日待機

影響調査

会社では東京のIDC(ハウジング)とさくらのクラウドを併用しており、どちらにあるサーバーも主にLinux系OSで動いています。

Linuxサーバーのカーネルバージョンを確認したところ下記いずれかでした。

  • 3.12, 3.18等比較的新しいカーネル
  • 対策済みの2.6系カーネル

大丈夫そうですね。

一方ntpdは一部古いものがありましたので下記いずれかのバージョンにアップデートしました。

  • ntpd 4.2.8@1.3265-o
  • ntpd 4.2.6p5@1.2349-o

続いてネットワーク機器ですが、保守してくださってるベンダー様から連絡をいただきまして、ハウジングで使用しているCiscoのNexusというスイッチが影響を受けることがわかりました。

ネットワーク機器の対策

Cisco Nexusは前後1日程度バッファを取って下記のオペレーションで対応することになりました。

  • 6/29 にNTP設定削除
  • 7/2 にNTP設定復元

Slewモードで動くNTPサーバーの構築

この機会にNTPの設定も整理しよう!ということでNTPサーバーを新設しました。

# uname -r
4.0.5-gentoo
# ntpd --version
ntpd 4.2.8@1.3265-o Sat Jun 27 17:13:03 UTC 2015 (1)
# ps aux | grep 'ntp[d]'
root      2871  0.0  0.0 102768 14852 ?        SLs  19:17   0:00 /usr/sbin/ntpd -p /var/run/ntpd.pid -g -x
# ntpq -p
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
+ntp1.jst.mfeed. 133.243.236.17   2 u   66  128  377    0.538    2.622   0.016
*ntp2.jst.mfeed. 133.243.236.17   2 u  125  128  377    0.542    1.133   0.050
+ntp3.jst.mfeed. 133.243.236.17   2 u   90  128  377    0.734    1.299   0.062

意気込んで新設した結果カーネル4系になりました!
ピカピカですね!

サーバーのNTP設定変更

全サーバーのNTP設定を新設したNTPサーバーを見るように変更します。
Ansibleで設定ファイルを置き換えていくだけなので楽チンです。

当日待機

当日は念のため待機しました。
前回はけっこう大変なことになってたので、今度は自分が対応に追われる番かと覚悟もしていたのですが、幸い今回はとくに大きな異常は発生せずに9時(JST)を過ぎました。
でもちょっとくらい問題起こってもおもしろかったかも

さくらのクラウドでも事前にしっかりと対策していただいていたようでクラウド基盤での異常も皆無でした。

何事もなかったことをSlackで報告し意気揚々と帰路に着くことができました。
これもLinuxや各コミュニティ・企業の皆様のおかげですね!

f:id:mazgi:20150702123420p:plain

ただ、帰宅すると自宅のMacがフリーズしていました。
原因につきましてはわかりかねました。

参考にさせていただいたページ

対策実施および本記事を書くにあたっては下記ページを参考にさせていただきました。