Edited at

cloud-initを使ったLinux OSの初期設定


1. はじめに

Computeインスタンス作成時にユーザーデータ(User Data)を指定したことがあるだろうか。ユーザーデータを使うと、簡単にLinux OSを設定できる。便利な割に使われていない気がするので、ユーザーデータ/cloud-initについて説明する。


1-1. TL;DR


  • cloud-initを使うと、簡単にLinux OSの初期設定ができる

  • cloud-initの仕組みは各所で解説されているが、利用者にとってコンパクトで実践的なものは少ない

  • 実践的なサンプルや、自力で書くときの参考情報を提供することで、活用のきっかけになりたい


1-2. 前提条件


  • cloud-initが利用可能なクラウド・サービス

  • Linux OSイメージ


2. なぜ、cloud-init ?

IaaSにおいて、本格的にOS設定の自動化や、Infrastructure as Codeを目指すならばAnsibleやChef、Puppet等の構成管理ツールが最適だ。またWebサーバなど、同一構成のサーバーをたくさん複製するにはカスタム・イメージ(カスタムAMI)が適している。

ただし、実際の現場では、Ansibleなどの構成管理ツールを導入するほどではないけれど、ちょっとしたOS設定を変更したいことがある。

そのようなときに便利なのがcloud-initだ。もともとはAmazon EC2用に開発されたツールだが、現在はクラウドでLinux OSを設定する一般的なツールになっている。

次の図はユーザーデータ(User Data)とcloud-initの関係を表した図だ。インスタンス作成時にユーザーデータを指定すると、OSイメージに組み込まれたcloud-initがユーザーデータの内容を実行することで、OSの初期設定ができる。

ユーザーデータ(User Data)とcloud-initの関係

cloud-init01.png


3. cloud-initでできること


3-1. cloud-initで指定できる書式

cloud-initでは8種類の書式を使用できる。その中でも、よく使用するのはシェルスクリプトとcloud-configだ。シェルスクリプトは当たり前なので省略するとして、今回はcloud-configについて説明する。

cloud-initでよく使われる書式

書式
特徴

シェルスクリプト
既存のノウハウや資産を生かせる。#!で始まるスクリプトならば実行可能

cloud-config
YAML形式で簡潔に書け、OSの差異を吸収できる

MIMEマルチパート形式
シェルスクリプトとcloud-initを同時に使うなど、複数の書式を併用できる


3-2. cloud-configとは

cloud-configとは、YAMLで簡潔にOS設定を記述できる機能だ。次の例ではタイムゾーンを東京に設定している。

#cloud-config

timezone: Asia/Tokyo

ここで注目することが二つある。一つ目は#cloud-configで始まっていること。これはcloud-configの書式で記述することを宣言している。

二つ目はタイムゾーンの指定方法。たとえばCentOS 6とCentOS 7ではタイムゾーンの設定方法は異なる。ところがcloud-configのtimezoneモジュールがOSの違いを吸収するため、まったく同じ記述を利用できる。

またtimezoneモジュールは複数のLinuxディストリビューションに対応しているので、RHEL系だけでなくUbuntuでも利用できる。


3-3. cloud-initの実行タイミング

基本的には「インスタンス作成後の初回起動時に1回だけ実行する」と覚えていいだろう。例外もあるのだが、今回説明する範囲は、この理解で問題ない。

もう、ひとつ覚えておくべきことは「OSのインストールが終わったあとに実行される」ことだ。1回だけ実行される/etc/rc.localのようなものと考えてもいいだろう。

Linuxのサイレント・インストール・ツールKickstartとは異なるタイミングで実行されることは覚えておきたい。詳細は以下のエントリを参照のこと。


4. cloud-configチュートリアル基本編

それではcloud-configの記述例を紹介する。今回の環境では以下のバージョンを使用している。

# rpm -q cloud-init

cloud-init-18.4-1.el7.x86_64


4-1. cloud-config作成の基本

cloud-configを作成する手順は以下のとおり。


  1. cloud-initマニュアルページ設定サンプルページモジュール一覧から、やりたいことを実現できそうなサンプルやモジュールを探す。以下の画面ショットには少ししか表示されていないが、50以上のモジュールがある。

    cloud-init-docs01.PNG


  2. モジュールのページで、自分が使うディストリビューションに対応しているか確認する。次のようにallになっていれば大丈夫だ。



Supported distros: all


また以下のようにonce-per-instanceと書かれていれば、初回起動時に1回だけ実行される。


Module frequency: once-per-instance


基本を理解したところで、次からは実際のサンプルを紹介しよう。


4-2. タイムゾーンとロケールを設定する

この設定ではタイムゾーンを東京、ロケールを日本語に設定している。timezoneもlocaleも複数のディストリビューションに対応しているので汎用的に利用できる。

#cloud-config

timezone: Asia/Tokyo
locale: ja_JP.utf8


4-3. パッケージを追加する

パッケージを追加インストールするときには、次のようにpackagesのあとにパッケージ名を記述する。

#cloud-config

timezone: Asia/Tokyo
locale: ja_JP.utf8
packages:
- man-pages-ja
- multitail
- nmap
- nc
- tmux
- tree

ただしパッケージの追加やアップデートは、メタデータの同期やインストールに時間がかかることがある。そのためインスタンスの起動直後は実行されていない可能性がある。

実行状況は/var/log/cloud-init.logで確認できる。次のようにfinish: modules-finalが出力されれば終了している。

# tail -f /var/log/cloud-init.log

★ここから下は画面の出力★
2019-05-16 13:04:25,343 - util.py[DEBUG]: Read 14 bytes from /proc/uptime
2019-05-16 13:04:25,343 - util.py[DEBUG]: cloud-init mode 'modules' took 0.208 seconds (0.21)
2019-05-16 13:04:25,344 - handlers.py[DEBUG]: finish: modules-final: SUCCESS: running modules for final


4-4. パッケージをアップデートする

yum updateを実行するときは、以下のようにpackage_upgradeの行を追加する。

#cloud-config

timezone: Asia/Tokyo
locale: ja_JP.utf8
package_upgrade: true
packages:
- man-pages-ja
- multitail
- nmap
- nc
- tmux
- tree


4-5. 実行後に自動で再起動する

パッケージをアップデートすると、kernelやglibcが更新される可能性がある。そのときに便利なのがpower_stateだ。cloud-init実行後に自動で再起動できる。

以下のようにdelay:"+10", timeout:30と指定した場合、cloud-init終了後30秒間のタイムアウト時間ののち、10分後に再起動される。

Oracle Linuxの場合、Kspliceのためにkernelアップデートに時間がかかるので、delayは5分未満に設定しないことを推奨する。

#cloud-config

timezone: Asia/Tokyo
locale: ja_JP.utf8
package_upgrade: true
packages:
- man-pages-ja
- multitail
- nmap
- nc
- tmux
- tree
power_state:
delay: "+10"
mode: reboot
message: Bye Bye
timeout: 30


5. cloud-configチュートリアル応用編

cloud-initのマニュアルを見ると分かるが、現時点ではcloud-configのモジュールの数が少なく設定できない項目も多い。そのためシェルスクリプトを書きたくなるが、このとき三つの選択肢がある。


  1. cloud-configをやめて、全部シェルスクリプトで書く

  2. MIMEマルチパート形式で書いて、cloud-configとシェルスクリプトを併用する

  3. cloud-configのruncmdを使用する

このなかで便利なのがruncmdだ。制限事項はあるがスクリプトを記述できる。つまりcloud-configの利便性と、シェルスクリプトの自由度の高さを活用できるのだ。したがって、runcmdの使い方を説明する。

なお今回は取り上げないが、MIMEマルチパート形式はcloud-utilsパッケージのwrite-mime-multipartコマンドを使えば作成できる。


5-1. runcmdの使い方

マニュアルに載っているサンプルは以下のとおり。ハイフン以降の各行が、それぞれのコマンドである。

runcmd:

- [ ls, -l, / ]
- [ sh, -xc, "echo $(date) ': hello world!'" ]
- [ sh, -c, echo "=========hello world'=========" ]
- ls -l /root
- [ wget, "http://example.org", -O, /tmp/index.html ]

この例を見ても分かるように、カギ括弧以降に配列で書く方法と、ハイフン以降に直接書く方法がある。後者の場合にはshで実行したとみなされる。またいずれの場合もroot権限で実行され、YAML形式なのでコロン(:)は必ずクオートする必要がある。

分かりやすさの点から、今回は直接記述する方法を採用する。

なおruncmdは約100行のPythonスクリプトなので、興味のある人は読んでみては。

/usr/lib/python2.7/site-packages/cloudinit/config/cc_runcmd.py


5-2. SELinuxを無効にする

OCI ComputeではデフォルトでSELinuxが有効になっている。runcmdでSELinuxを無効化する。

#cloud-config

timezone: Asia/Tokyo
locale: ja_JP.utf8
runcmd:
- setenforce 0
- sed -ie 's/^\SELINUX=enforcing/SELINUX=disabled/' /etc/sysconfig/selinux


5-3. ファイアウォールを無効にする

OCI Computeではデフォルトでfirewalld/iptablesが有効になっている。SELinuxに加えてファイアウォールを無効化する。ただし6系と7系では設定が異なる。

Oracle Linux 7/CentOS 7の場合

#cloud-config

timezone: Asia/Tokyo
locale: ja_JP.utf8
runcmd:
- setenforce 0
- sed -ie 's/^\SELINUX=enforcing/SELINUX=disabled/' /etc/sysconfig/selinux
- systemctl stop firewalld
- systemctl disable firewalld

Oracle Linux 6/CentOS 6の場合

#cloud-config

timezone: Asia/Tokyo
locale: ja_JP.utf8
runcmd:
- setenforce 0
- sed -ie 's/^\SELINUX=enforcing/SELINUX=disabled/' /etc/sysconfig/selinux
- service iptables stop
- chkconfig iptables off


6. クラウドサービスでの指定方法


6-1. OCI Computeでユーザーデータ(User data)の指定方法


  1. Compute作成ページの一番下で[Show Advanced Options(拡張オプションの表示)]をクリックする。

user-data01.PNG

2.ページが拡張され、User dataを入力できるようになる。ファイルを指定するか、直接スクリプトを貼り付ける方法がある。

user-data02.PNG


7. おわりに

ここまで読んで

「おれはインスタンス作成後に、コマンドのコピペでOK」

と思う人もいるかも知れない。筆者も過去のエントリで、コマンドのコピペ用バージョンを紹介している。

cloud-initの魅力は、インスタンス作成と同時に設定できること(fire and forget)と、汎用性が高く簡潔に書けることだろう。社内やグループ内のポータルなどに典型的なパターンを紹介しておけば再利用されることも多いのではないだろうか。

まとめ


  • ユーザーデータ/cloud-initを使用すると、インスタンス作成時にLinux OSを設定できる

  • シェルスクリプトやcloud-configなど複数の書式を利用できる

  • cloud-configという書式ではシンプルで汎用性の高い記述ができる

  • rumcmdを使うと、cloud-conifg内にOSコマンドを記述できる

  • cloud-initはインスタンス作成後に実行されるので、時間のかかる処理を記述すると、すべての設定が反映されるまでタイムラグがある


8. 参考

自分で書いた関連エントリー