話題の記事

Mac OS Xの環境構築を自動化する(2016年度初旬編)

2016.04.04

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

中山です。自己紹介ブログ以外では一発目のエントリです。よろしくお願いします。

唐突ですが、みなさんMacの環境構築はどのように行っていますか。温かみのある手作業で行っていますか。または、何らかのツールを利用して自動的に設定が行えるような仕組みで構築していますか。

私はこの作業にAnsibleを利用しています。Ansibleを利用したMacの環境構築というと、去年話題になっていましたね。こちらのエントリが火付け役だったと記憶しています。その後も、エンジニア界隈で定期的に話題になるネタのようです。

私は結構前からこの方法で環境構築を行ってきたということもあって、そこそこ知見が溜まってきました。また、時期的に(新入社員の季節!私もです)会社からPCを支給され初期構築に四苦八苦されている方々が多いのではないでしょうか。

そこで本エントリではAnsibleを利用したMacの環境構築2016年度版という内容で一本ブログを書いてみようかと思います。

Ansibleを使用するメリット

詳細な説明に入る前にAnsibleを利用するメリットについて記述します。主なメリットは以下の点だと考えています。

  • Mac用の各種モジュールが存在する
  • ソフトウェアのインストールだけではなく設定変更も可能
  • 冪等性がある
  • 導入が(その他の構成管理ツールと比べて)比較的容易
  • AnsibleのplaybookをGitHubなどで管理することにより、環境の再現が容易になる

例えば「ソフトウェアのインストールだけではなく設定変更も可能」の例を上げます。私はよくHashiCorpのツールを利用しています。それらツールのzsh補完関数も利用しているのですが、補完関数がどこにあるのかというと、例えばterraformの場合、リポジトリの中にあるんですね。

このコードを初期構築毎に取得して、zshのfpathが通ったディレクトリに置くという作業は苦行でしかないので、この辺の設定も自動で行って欲しいです。

それができるんです。そう、Ansibleならね。

環境

コードはGitHubで管理しています。「Yosemite/El Capitan」で動作確認をしています。

Ansibleの実行方法

READMEにOSが初期状態からの実行方法を(拙い英語で)書いているのですが、簡易的にまとめると以下のとおりです。

まず、リポジトリをcloneします。

$ git clone https://github.com/knakayama/mac-os-x-setup.git && cd mac-os-x-setup

次にAnsibleをインストールします。実行に必要なモジュールは「requirements.txt」に書いてあるのでそこからインストールします。

$ pip install -r requirements.txt

実行します。sudoのパスワードを聞かれるので入力してください。

$ ansible-playbook site.yml -vvvv --ask-become-pass

コードの解説

コードを理解するポイントとなるものをピックアップして以下に解説します。

全体の構造

以下のような感じになっています。

├── README.md
├── ansible.cfg
├── bin
│   └── hosts.py
├── group_vars
│   └── localhost.yml
├── localhost.yml
├── requirements.txt
├── roles
│   ├── env
│   │   ├── defaults
│   │   │   └── main.yml
│   │   └── tasks
│   │       ├── env.yml
│   │       └── main.yml
│   ├── homebrew
│   │   ├── defaults
│   │   │   └── main.yml
│   │   └── tasks
│   │       ├── homebrew.yml
│   │       └── main.yml
│   ├── lang
│   │   ├── defaults
│   │   │   └── main.yml
│   │   ├── handlers
│   │   │   └── main.yml
│   │   └── tasks
│   │       ├── gem.yml
│   │       ├── go.yml
│   │       ├── main.yml
│   │       ├── npm.yml
│   │       ├── pip.yml
│   │       └── pip3.yml
│   └── mac
│       ├── defaults
│       │   └── main.yml
│       ├── files
│       │   ├── Library
│       │   │   ├── Application\ Support
│       │   │   │   └── Karabiner
│       │   │   │       └── private.xml
│       │   │   └── Preferences
│       │   │       └── com.googlecode.iterm2.plist
│       │   └── bin
│       │       └── karabiner.sh
│       ├── handlers
│       │   └── main.yml
│       ├── tasks
│       │   ├── mac.yml
│       │   └── main.yml
│       └── templates
│           └── Library
│               └── LaunchAgents
│                   ├── local.knakayama.compresstmuxlog.plist.j2
│                   ├── local.knakayama.getnewmail.plist.j2
│                   └── local.knakayama.homebrewupdate.plist.j2
└── site.yml

基本的にはAnsibleの公式ディレクトリレイアウトに従いつつ、適宜自分好みに修正するという構成にしています。

大本となるplaybookが「site.yml」です。このファイルから「localhost.yml」をインクルードさせています。コードはこちらです。

このファイルでroleを定義し、roles以下のroleが順次呼び出されていくという仕組みになっています。

ダイナミックインベントリ

みなさんはダイナミックインベントリを利用されていますか。大規模な環境で使うもの、という印象をお持ちの方もいらっしゃるかと思うのですが、今回のような対象ホストが一つの場合でも結構便利な点があります。

コードはこちらです。私は主にAnsibleを実行するpythonのパスを動的に取得するために利用しています。行数で言うと8行目です。

osモジュールでpythonのパスを取得して、それを変数に代入しています。_meta属性の中で、「ansible_python_interpreter」に設定してあげることでパスを指定しています。_meta属性についてはこちらを参照してください。

こうすることで、pyenv経由のpipでAnsibleをインストールした場合でも、「そんなパスないで!」と怒られずに済むという仕組みにしています。

「ansible_python_interpreter」の設定をインベントリファイルに直接書くこともできるのですが、ハードコードするのはちょっと恥ずかしいので上記のようにしてます。

各種roleについて

以下ではそれぞれのroleについて解説します。

homebrew

HomeBrewを利用して必要な各種ソフトウェアをインストールしているだけです。taskとなるコードはこちらです。

Ansibleにはhomebrew/homebrew-tap/homebrew-caskを操作するためのモジュールがあるので、これを利用しています。標準モジュールなので特に追加でモジュールをインストールする必要はありません。

実行内容は上からそれぞれ以下のとおりです。

  1. /usr/localディレクトリの所有者情報修正
  2. homebrew自体のインストール
  3. homebrew tapの設定
  4. homebrewのupdate
  5. homebrew caskのインストール
  6. homebrewのインストール

env

主にdotfile関連の処理をしています。taskとなるコードはこちらです。

実行内容は上からそれぞれ以下のとおりです。

  1. 必要なリポジトリをclone
  2. dotfileのシンボリック貼り
  3. neovim用のディレクトリ作成
  4. 各種シンボリックリンク貼り
  5. dbxcliのインストールとzsh補完関数の設定
  6. apexのインストールとzsh補完関数の設定
  7. ログインシェルの変更

私はzshの補完関数を「~/.zsh/completions」というディレクトリに入れています。このディレクトリをfpathに入れることで動作させています。先ほどterraformの例を出しましたが、この場合補完関数がリポジトリの中に含まれているので、そのリポジトリを適当なディレクトリにcloneさせ、その中の補完関数へのシンボリックリンクを先ほどのディレクトリ下に作成させています。

こうすることで補完関数を動作させつつ、更新があった場合でもgit pullすれば最新のコードを取得できるようにしています。

mac

Mac特有の設定を行っています。taskとなるコードはこちらです。

Ansibleには2.0からosx_defaultsというモジュールが入りました。このモジュールを利用すればOS Xのdefaultsの変更が可能です。しかも、冪等性を保ちつつです。

実行内容は上からそれぞれ以下のとおりです。

  1. iTerm2の設定ファイル設置
  2. Karabinerの設定ファイル用ディレクトリ作成
  3. Karabinerの設定ファイル設置
  4. getmailのplist設置(pauseで選択できるようにしている)
  5. ディレクトリが深いところにある便利ツールを/usr/local/bin以下にシンボリックリンク貼る
  6. Rictyフォントのインストール
  7. defaultsの設定

みなさんはAnsibleのplaybookを書いている時、「特定の条件を満たした場合のみshellモジュールを実行させる」という処理にしたい場合、どのようにコードを書いていますか。

一番簡単なのは「creates」や「when」ですね。ただ条件が複雑になる場合はこれだけだと書きにくい場合があります。

そこで本命のtaskの前にチェック用のtaskを書くという方法が存在します。例えば以下のような感じです。

# tasks/main.yml
- name: Check if hoge in fuga
  shell: |
    grep -F 'hoge' path/to/fuga
  register: res
  always_run: yes
  failed_when: no
  changed_when: res.rc == 0

- name: Ping only if hoge in fuga
  ping:
  when: res|changed

これでも意図した動作はするのですが、コード的にどうにも冗長な感じがしませんか。なので私は最近「notify」と組み合わせる方法を使っています。

# tasks/main.yml
- name: Check if hoge in fuga
  shell: |
    grep -F 'hoge' path/to/fuga
  register: res
  always_run: yes
  failed_when: no
  changed_when: res.rc == 0
  notify:
    - Ping only if hoge in fuga
# handlers/main.yml
- name: Ping only if hoge in fuga
  ping:

こちらの方がロジックの見通しが良くなりませんか。ならないですが、そうですか。。。

「notifyに設定するとtaskに失敗した場合など実行されない時がある」と思う方もいるかもしれません。「notify」を即実行させたい場合は、 meta: flush_handlers使えます

実際の例で言うと2行目のtaskです。

lang

各種言語の設定を行っています。といっても、各種言語で記述されたコマンドラインツールなどのインストールをしているだけです。モジュールのインストールなどはプロジェクト毎にディレクトリを分けて設定することが多いので、初期構築時には設定していません。

実行内容は上からそれぞれ以下のとおりです。

  1. gemの設定
  2. goの設定
  3. pipの設定
  4. pip3の設定
  5. npmの設定

Ansibleには各種言語用パッケージマネージャのモジュールがあります。上記の例だとgem/pip/npmを利用しています。

コードを継続的にアップデートする

こちらのコードは初期構築時に利用して終わりではありません。Macへ新しい変更を加える場合は常にアップデートを続ける必要があります。そうしないと、いざ新しいPCの設定をする場合に、内容が古すぎて使い物にならなくなってしまうからです。

「管理コスト高そうだな」と思った方もいるかもしれません。が、自分的には苦行だとは感じてません。すこしYAML書くだけで作業が完結するからです。

そう、Ansibleならね。

まとめ

いかがだったでしょうか。Ansibleの一番のメリットはその容易さだと考えています。初めてplaybookの内容を見た方でも、ソースコードを見るだけで実行される内容が結構分かったのではないでしょうか。シンプルさは正義。

実際のサービスにAnsibleを投入する前の肩慣らし的な目的としても、Macの環境構築に導入を検討してみるのはありなのではと考えています。本エントリがみなさまの参考になれば幸いに思います。