LoginSignup
24
30

More than 5 years have passed since last update.

Rails開発環境をAnsibleでサクッと構築する(Ruby, MySQL, Node.js, Redis, ElasticSearch)

Last updated at Posted at 2018-04-28

!TL;DR

Ansible知ってるandソースだけ欲しい人はここで終了です。
https://github.com/Blue-Pix/eclair

なぜeclairという名前で作ったかというと、Ansibleの語源からインスピレーションを得ました。

Ansible is 何?

インフラのオートメーションツール。
ChefとかPuppetと並んで引き合いに出されます。
ansible-logo.png

環境構築 is 時間の無駄

・プロジェクトごとに毎回Vagrant構築するのしんどい
・テキストベースで環境構築マニュアル作るのしんどい
・コマンド1つ1つメモるのか
・構築するものが多い(Ruby, Node.js, MySQL, ElasticSearch, Redis, ...etc.)
・プロジェクトごとに細かいバージョン違って死ぬ

Why not Chef ?

ざっくりChefとAnsibleについて調べた結果、

  • 導入コストが少なそうだったから
  • エージェントレスだから(対象サーバーにPythonさえ入ってれば構築できる)
  • シンプルだから
  • Pythonとyamlで書くから(僕はRubyが苦手です)

です。

Ansible

  • yamlで書く
  • エージェント不要
    • ssh経由で構築を行う
    • Pythonがインストールされてる環境ならOK
  • build and scrapに向いてる

Chef

  • Rubyで書く
  • Chef-Serverとかknifeとか特有のツールがいる
  • 複数台のホスト管理に向いてる(というか前提?)

サクッと開発環境を作るならAnsible一択かなあと思います。

今回の目的

macOS High SierraCentOS7.2のVagrantを立てて、
以下をインストールします。

  • Ruby 2.4.2
  • MySQL5.7
  • Node.js 8.1.2
  • Redis stable
  • ElasticSearch 5.6.8

なぜこのバージョンかと言うと、携わるプロジェクトがこの構成だったからです。
バージョンはそれぞれ好みのものに変えれる用に作ってはありますが、
検証はしてないので互換性がないやつは普通にエラー出ると思います。

MySQL5.6, 5.7に対応させてありますが、5.1とかはたぶん無理です。
パスワード周りが5.65.7で地味に違うのめんどくさいよね。
Rubyrbenv, Node.jsnを使って入れます。

RailsアプリはGit clone済みで、Vagrantにマウントする想定です。

OSはバージョン以上に深刻な問題で、CentOS7系以外では無理です。
どうしてもUbuntuで開発したいんだ俺は!!という方はせっせと
yumapt-getに書き換えたりして対応してください。
設定ファイルの位置とかsystemdとか細かく違うので同じCentOSと言えど6系も無理です。

VMの構築

Vagrantはインストールしておいてくださいね。

$ vagrant box add CentOS7.2 https://github.com/CommanderK5/packer-centos-template/releases/download/0.7.2/vagrant-centos-7.2.box
$ vagrant init CentOS7.2
VagrantFile
# コメントアウトを外す
config.vm.network "private_network", ip: "192.168.33.10"

# フォワードしたいポートを追記する
config.vm.network "forwarded_port", guest: 3000, host: 30000
config.vm.network "forwarded_port", guest: 3035, host: 30350

# マウントするフォルダを追記する
config.vm.synced_folder "./app/my_app", "/home/vagrant/my_app", owner: "vagrant", group: "vagrant"

マウントするRailsアプリと今回の環境構築のフォルダは
同じ階層に置いてます。プロジェクトごとにフォルダ作って、
アプリと構成管理をそれぞれgit cloneしてくる流れで開発してます。

VagrantFileは元々あるVagrantFile.exampleをコピーして作ってください。
ここで1回起動しておきます。

$ vagrant up

Ansibleはssh経由でリモートホストの環境を構築するので、
sshでこのVMにつなげるように手配して置く必要があります。

VMのホスト名をhogeとして、sshの接続情報をmacのホームディレクトリに記述します。
~/.ssh/configに書くか、プロジェクトごとに別ファイルを切るかはお好みだと思います。
どこに書こうが特に支障はありません。

$ vagrant ssh-config --host hoge >> ~/.ssh/hoge-config

sshでログインできるか確認

$ ssh hoge
Last login: Sat Jan  6 19:39:59 2018 from 10.0.2.2
[vagrant@localhost ~]$

構築終わり。
開発はデフォルトのvagrantユーザーで進めるので、
別ユーザーでやりたい場合は適宜作ってください。

Ansibleのインストール

brew一発です。

$ brew install ansible

$ ansible --version
ansible 2.4.2.0

$ ansible-playbook --version
ansible-playbook 2.4.2.0

Ansibleの初期設定

どのソフトをインストールして設定ファイルをこう書き換えて〜
みたいなことはPlaybookと呼ばれるファイルに記述するのですが、
それとはまた別のレイヤーで、どのホストに対してどういう構成を当てるかや、
変数の宣言などをあらかじめしておかなければならないのでそこの設定です。

inventory file

対象となるホストを記述するファイルです。
yamlではなくINI形式。
ファイル名に決まりはないですが、hostsという名前が一般的です。
グループ名とホスト名の対ですが、グループ名は省略できます。

hosts
hoge

WebサーバーやらDBサーバーやら一緒くたにするので
グループ化する必要はないのでホスト名だけ。
Docker的な思考でサービスごとに分離したい時や、
本番環境で運用する場合はこの辺り整備する必要があります。
今回はローカルでの開発用なので思考停止してください。

Ansibleを本番で使おうとする場合は、
もっともっともっともっと考え抜いて設計しなければいけません。

ansible.cfg

Ansibleの設定ファイルです。
ansibleコマンドを使うときにオプションとしても指定できますが、
ファイルで定義していた方が楽なので。

ansible.cfg
[defaults]
inventory = hosts
retry_files_enabled = False

[privilege_escalation]
become = True

[ssh_connection]
control_path = %(directory)s/%%h-%%r
ssh_args = -o ControlPersist=15m -F /Users/hoge/.ssh/hoge-config -q
scp_if_ssh = True

[defaults]
inventory: 先ほど定義したホスト情報のファイル名を指定
retry_files_enabled: 構成に失敗した時に.retryファイルを作るかどうか

[privilege_escalation]
become: 管理者権限で実行するかどうか

[ssh_connection]
control_path: コントロールソケットを保持するパス
ssh_args: sshコマンドの引数
scp_if_ssh: scpでファイルを転送するかどうか

重要なのはsshの接続情報をちゃんと参照できるようにするのと、
inventoryでホスト情報を指定すること。
また、become: Trueで管理者権限でサーバーをいじるようにすること。
vagrantユーザーでは触れないところがあるので、基本はroot権限で
インストールやら何やかんやをして、必要に応じてvagrantユーザーに
戻るという手順を取ることにする。

▼参考
https://dev.classmethod.jp/server-side/ansible/ansible-tutorial-2017/

▼他にもたくさんオプションがあるので詳しくは公式を
http://docs.ansible.com/ansible/latest/intro_configuration.html

▼スケルトンがあるのでそれを使うのもあり
https://raw.githubusercontent.com/ansible/ansible/devel/examples/ansible.cfg

site.yml

Playbookというやつです。
ここにどのhostに対してどのtaskを実行するかを定義します。

site.yml
- hosts: hoge
  roles:
    - init
    - mysql
    - ruby
    - nodejs
    - redis
    - elasticsearch

taskはベタ書きするのではなく、roleという単位にまとめてます。
1role1middlewareのイメージです。
こうすることで見通しも良くなるし、
roleごとに再利用可能になり、
このプロジェクトではredis使わないとかなったら

- redis

だけコメントアウトすればよくなります。

Ansibleの実行

hosts, ansible.cfg, site.ymlが書けたら、
以下のコマンドを打つだけで構築が完了します。

$ ansible-playbook site.yml

Rails開発環境できましたね。

これ以降はそれぞれのroletaskについて
Ansibleの解説になるので興味ない人は読み飛ばしちゃってください。

変数について

Ansibleにおいて変数を定義できる場所はたくさんあります。
ゆえに散らばらせるとたくさん上書きされてよくわからないことになります。
シンプルにするために以下のように自分ルールを定めます。

  • roleに依存するものはroles/xxx/vars/main.ymlに定義する。
roles/redis/vars/main.yml
__version: stable
__port: 6379
__data_dir: /var/lib/redis
__log_dir: /var/log/redis
__log_level: notice

versionなんかほとんどのミドルウェアで使う変数名だと思うので、
グローバルに定義すると上書きが起こってしまい不安です。
ミドルウェアごとにプレフィックスをつけるのもありっちゃありですが、
そもそもスコープをrole内に絞るのが自然かと思います。

あ、プレフィックスに__を付けているのはpythonicな習慣です。
あらかじめ定義してあり、代入は禁止ということを表したいのでこういう書き方を取りました。
というのも、taskの中で標準出力を変数に格納する場面があるのですが、
タスク実行中に定義された変数と、あらかじめ定義してある変数をごっちゃにしたくないのです。

また、変数を使う時には基本ブラケットで括るのですが({{ __version }})、
whenオプションの中などではWarningが出るため、生で書かなければいけない時があります。

when: __version == 5.6

__がないと変数であることが非常に分かりくいので可読性のためにもつけることにしてます。

when: version == 5.6
  • roleをまたいで使うものはsite.ymlに定義する。
site.yml
- hosts: hoge
  roles:
    - init
    - mysql
    - ruby
    - nodejs
    - redis
    - elasticsearch
  vars:
    __working_user: vagrant

__working_userはいろんなroleで参照しているのでグローバルにここで定義します。

色々とやり方はありますが自分好みの構成はこれです。
変数の管理とか、ディレクトリ構成は以下の記事が参考になると思います。
https://qiita.com/kkitta/items/27f8aca89e55b719cb6f
https://qiita.com/ArimaRyunosuke/items/1f9d840311584d8160bc
https://qiita.com/kotarella1110/items/79af4485bd7985935d6b

または公式のBest Practiceに倣う。

Rolesの作成

ではでは具体的なtaskの記述について。

init

.bash_profileの配置と、ロケール設定です。

色々とインストールをしていく中でPATHがカオスなことにならないように、
以下の記事のようなフラグメント化を行なっています。
.bash_profileはフラグメント化してプロビジョニングしやすくしよう!

roles/init/tasks/main.yml
- name: deploy .bash_profile
  template:
    src: .bash_profile
    dest: ~/.bash_profile
    mode: 0644
  become_user: "{{ __working_user }}"

roles/init/templatesにデフォルトの.bash_profileを用意して、
vagrantユーザーのホームに置く。実行時はrootユーザーなので、
become_userでvagrantユーザーに成りすます。

roles/init/tasks/main.yml
- name: mkdir .bash.d
  file:
    path: ~/.bash.d
    state: directory
    mode: 0755
  become_user: "{{ __working_user }}"

ディレクトリの作成にはFilesモジュールのstate: directoryを使う。
shell: mkdir ~だと自分で冪等性を管理しなくてはいけないので面倒。
Ansible組み込みのモジュールを使うと自動的に冪等性を保証してくれるので積極的に使う。(yumなど)

roles/init/tasks/main.yml
- name: stop and disable firewalld
  service:
    name: firewalld
    state: stopped
    enabled: no

ファイアウォールを止める。
systemctlコマンドはSystemモジュールのserviceで扱える。

roles/init/tasks/main.yml
- name: set LANG={{ __locale }} in /etc/locale.conf
  lineinfile:
    dest: /etc/locale.conf
    regexp: "^LANG="
    line: "LANG={{ __locale }}"
    backup: yes

ロケールを日本語にする。
すでにある設定ファイルなどの中身を書き換える時はFilesモジュールのlineinfileで正規表現を使って置換する。
一応backup: yesで破壊的な変更を防ぐ。

MySQL

個人的にMySQL入れるのが一番めんどくさい。
パスワードポリシーとかバージョンによって違ったり、
community serverどうやって入れるんだっけとか毎回調べてる気がする。
Ansibleで書くときも厄介なのがパスワード問題。

roles/mysql/tasks/main.yml
- name: check if pre-installed mariadb exists
  yum:
    list: mariadb-libs
  register: mariadb

デフォルトでmariadbが入っているかどうかチェックします。
結果はmariadbという変数に格納します。

roles/mysql/tasks/main.yml
- name: uninstall pre-installed mariadb
  yum:
    name: mariadb-libs
    state: absent

最初から入ってるmariadbをアンインストールする。
yumモジュールを使うのでアンイストール済みかどうかは勝手にチェックしてくれる。

roles/mysql/tasks/main.yml
- name: remove pre-installed mariadb's file
  file:
    path: /var/lib/mysql
    state: absent
  when: mariadb.results[0].yumstate == 'installed'

mariadbをアンインストールするときに、変な影響が出ないように関連するファイルも削除する。
whenオプションを付けているのは、冪等性のため。
2回目以降実行するときに、新しくインストールしたMySQLのファイルを削除してしまうのを防ぐため。

roles/mysql/tasks/main.yml
- name: add yum repository
  yum: 
    name: "{{ __repo_url }}"
    state: present

mysql57-community-release-el7-11.noarch.rpmのyumリポジトリを追加する。

roles/mysql/tasks/main.yml
- name: intall yum-utils
  yum:
    name: yum-utils
    state: present

バージョンによってリポジトリを切り替えなければいけないため、
yum-config-managerコマンドを使えるようにyum-utilsをインストールする。

roles/mysql/tasks/main.yml
- name: switch yum repository (for version 5.6)
  shell: |
    yum-config-manager --disable mysql57-community
    yum-config-manager --enable mysql56-community
  when: __version == 5.6 and mariadb.results[0].yumstate == 'installed'

- name: switch yum repository (for version 5.7)
  shell: |
    yum-config-manager --disable mysql56-community
    yum-config-manager --enable mysql57-community
  when: __version == 5.7 and mariadb.results[0].yumstate == 'installed'

バージョンによってリポジトリを切り替える。
shellで処理するので、2回目以降不要なchangedが出ないように、
mariadbをアンインストールした時(=初回実行時)のみ実行するようにする。

ちなみに!!!!
whenオプションの中で変数を{{ }}で括ろうとすると以下のエラーが出る。

 [WARNING]: when statements should not include jinja2 templating delimiters
such as {{ }} or {% %}.

見にくいが{{ }}なしで変数名を記述する。

roles/mysql/tasks/main.yml
- name: install MySQL{{ __version }}
  yum:
    name: "{{ item }}"
    state: present
  with_items:
    - mysql
    - mysql-devel
    - mysql-server
    - mysql-utilities
    - MySQL-python

色々インストールします。複数個処理するときはwith_itemsでスッキリ書く。

roles/mysql/tasks/main.yml
- name: start and enable server
  service:
    name: mysqld
    state: started
    enabled: yes

1回起動しておきます。自動起動の設定もenabledで有効に。

roles/mysql/tasks/main.yml
- name: check if my.cnf.bk already deployed
  stat:
    path: "/etc/my.cnf.bk"
  register: default_my_conf_backup

以降の処理の冪等性を保つため、my.cnfのバックアップが作られているかどうかを確認します。作成済みであれば、これ以降の処理は実行済みとみなしてスキップします。

roles/mysql/tasks/main.yml
- name: get default MySQL root users's password
  shell: |
    less /var/log/mysqld.log | grep password | awk '{ print $NF }'
  register: mysql_default_password
  when: not default_my_conf_backup.stat.exists and __version == 5.7

rootの初期パスワードがログに吐かれているので取り出します。5.6の場合はファイルが違うのでスキップ。

roles/mysql/tasks/main.yml
- name: change password policy
  shell: |
    mysql -u root -p'{{ mysql_default_password.stdout }}' --connect-expired-password -e "SET GLOBAL validate_password_length='{{ __password_length }}';"
    mysql -u root -p'{{ mysql_default_password.stdout }}' --connect-expired-password -e "SET GLOBAL validate_password_policy='{{ __password_policy }}';"
  when: not default_my_conf_backup.stat.exists and __validate_password and __version == 5.7

先ほど取り出した初期パスワードでログインして、シェル上からSQLステートメントを発行します。connect-expired-passwordオプションを付けないとログインに失敗するので注意。
ここではパスワードポリシーを変更する場合のみ、指定のポリシーに変更する処理を行っています。

また、バージョン5.6だとそもそもvalidate_passwordプラグインが有効になっていないのでスキップする。

roles/mysql/tasks/main.yml
- name: disable validate password
  shell: |
    mysql -u root -p'{{ mysql_default_password.stdout }}' --connect-expired-password -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '{{ mysql_default_password.stdout }}';"
    mysql -u root -p'{{ mysql_default_password.stdout }}' --connect-expired-password -e "uninstall plugin validate_password;"
  when: not default_my_conf_backup.stat.exists and not __validate_password and __version == 5.7

パスワードポシリーを無効化したい場合のタスクです。uninstall plugin validate_passwordで無効化できるのですが、先に初期パスワードをリセットしないとこのステートメントを実行することはできません。
適当なパスワードに再設定してもいいのですがシンプルさを保つため、
前述のファイルから取り出した初期パスワードでそのまま上書きます(笑)
これでもパスワードはリセットされたと認識してくれます。

roles/mysql/tasks/main.yml
- name: change root user password (for version 5.6)
  shell: |
    /usr/bin/mysqladmin -u root password "{{ __root_new_password }}"
  when: not default_my_conf_backup.stat.exists and __version == 5.6

- name: change root user password (for version 5.7)
  shell: |
    mysql -u root -p'{{ mysql_default_password.stdout }}' --connect-expired-password -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '{{ __root_new_password }}';"
  when: not default_my_conf_backup.stat.exists and __version == 5.7

rootユーザーのパスワードを指定のパスワードに変更します。
5.6と5.7でやり方が違う。5.7は前述のタスクでファイルから取り出したパスワードを使う。

roles/mysql/tasks/main.yml
- name: backup default my.cnf
  shell: |
    mv /etc/my.cnf /etc/my.cnf.bk
  when: not default_my_conf_backup.stat.exists

my.cnfのバックアップを作ります。

roles/mysql/tasks/main.yml
- name: deploy my.cnf
  template:
    src: "my.cnf_{{ __version }}.j2"
    dest: "/etc/my.cnf"
    owner: root
    group: root
    mode: 0644
  when: not default_my_conf_backup.stat.exists
  notify: restart mysqld

自分好みにカスタマイズしたmy.cnfを配置します。バージョンによって使えないオプションとかあるので注意。
配置したら設定を反映させるため、notifyを使って再起動を通知します。
handlerroles/xxx/handlers/main.ymlに書く。

roles/mysql/handlers/main.yml
- name: restart mysqld
  service:
    name: mysqld
    state: restarted
roles/mysql/tasks/main.yml
- name: create database [{{ __project_db_name }}]
  mysql_db:
    login_user: root
    login_password: "{{ __root_new_password }}"
    name: "{{ __project_db_name }}"
    state: present
    encoding: "{{ __project_db_encoding }}"
  when: __project_db_create

オプションでアプリのデータベースを作成します。今時のフレームワークはマイグレーションの仕組みがそれぞれあるので自分でデータベースを作成するのは稀かもしれませんが。
mysql_dbという便利なモジュールがあるのでそれを使います。

roles/mysql/tasks/main.yml
- name: create MySQL user [{{ __project_db_user_name }}] for database [{{ __project_db_name }}]
  mysql_user:
    login_user: root
    login_password: "{{ __root_new_password }}"
    user: "{{ __project_db_user_name }}"
    password: "{{ __project_db_user_password }}"
    host: localhost
    priv: "{{ __project_db_name }}.*:ALL,GRANT"
    state: present
  when: __project_db_create

さっき作ったデータベース用のユーザーを作成します。
同じくmysql_userというモジュールを活用します。(便利!)

Ruby

roles/ruby/tasks/main.yml
- name: install git and other dependencies
  yum:
    name: "{{ item }}"
    state: latest
  with_items:
    - git
    - nss
    - nss-util
    - nspr
    - curl

Ruby入れるために色々インストールします。

roles/ruby/tasks/main.yml
- name: install rbenv
  git:
    repo: https://github.com/rbenv/rbenv.git
    dest: ~/.rbenv
  become_user: "{{ __working_user }}"

rbenvインストールします。gitもモジュールあるよ!

roles/ruby/tasks/main.yml
- name: set global rbenv path
  template:
    src: 001_rbenv.sh
    dest: ~/.bash.d/001_rbenv.sh
    mode: 0755
  become_user: "{{ __working_user }}"

rbenvパス通します。
中身はこんな感じ。

roles/ruby/templates/001_rbenv.sh
export PATH=~/.rbenv/bin:~/.rbenv/shims:$PATH
eval "$(rbenv init -)"
roles/ruby/tasks/main.yml
- name: install dependencies for ruby build
  yum: 
    name: "{{ item }}"
    state: present
  with_items:
    - gcc
    - openssl-devel
    - libyaml-devel
    - libffi-devel
    - readline-devel
    - zlib-devel
    - gdbm-devel
    - ncurses-devel

Rubyビルドするために色々インストールします。

roles/ruby/tasks/main.yml
- name: install ruby-build
  git:
    repo: https://github.com/sstephenson/ruby-build.git
    dest: ~/.rbenv/plugins/ruby-build
  become_user: "{{ __working_user }}"

ruby-buildインストールします。

roles/ruby/tasks/main.yml
- name: make app directory (if rails new)
  file:
    path: "{{ __app_dir }}"
    state: directory
    mode: 0755
  become_user: "{{ __working_user }}"
  when: __rails_new

既存のRailsアプリがない場合、指定のバージョンでスケルトン作るのでアプリ用のディレクトリ作ります。

roles/ruby/tasks/main.yml
- name: check current ruby version
  shell: |
    /bin/bash -lc "ruby -v | grep -oP '^ruby \d{1}.\d{1}.\d{1}' | cut -f 2 -d ' '"
  args:
    chdir: "{{ __app_dir }}"
  register: current_ruby_version
  become_user: "{{ __working_user }}"
  changed_when: False

Rubyのバージョンチェックします。初回実行時はシステムデフォルトのバージョン(2.0とか)が取れるので、それでRubyインストールするかどうかを判断します。
一点注意すべきなのは、shell/bin/shを使うので、bashで通したPATHとかを使おうとしてもエラーになることです。/bib/bash -lc "コマンド"で明示的にbashシェルで実行します。

roles/ruby/tasks/main.yml
- name: shell relogin
  shell: |
    /bin/bash -lc  "source ~/.bash_profile"
  become_user: "{{ __working_user }}"
  when: current_ruby_version.stdout != __version

ログインし直してパス通します。

roles/ruby/tasks/main.yml
- name: install ruby
  shell: |
    /bin/bash -lc "rbenv install {{ __version }}"
    /bin/bash -lc "gem install bundler"
    /bin/bash -lc "rbenv rehash"
    /bin/bash -lc "rbenv local {{ __version }}"
  args:
    chdir: "{{ __app_dir }}"
  become_user: "{{ __working_user }}"
  when: current_ruby_version.stdout != __version

指定のバージョンのRubyインストールします。で、アプリのルートで使います。

roles/ruby/tasks/main.yml
- name: check rails new succeeded
  stat:
    path: "{{ __app_dir }}/Gemfile.lock"
  become_user: "{{ __working_user }}"
  register: rails_new_gemfile
  when: __rails_new

Railsアプリのスケルトンを作る場合のみ、すでにスケルトン作成済みかどうかチェックします。

roles/ruby/tasks/main.yml
- name: init bundler
  shell: |
    /bin/bash -lc "bundle init"
  args:
    chdir: "{{ __app_dir }}"
  become_user: "{{ __working_user }}"
  when: __rails_new and not rails_new_gemfile.stat.exists

- name: gem install rails
  lineinfile:
    path: "{{ __app_dir }}/Gemfile"
    regexp: '# gem "rails"'
    line: 'gem "rails", "{{ __rails__version }}"'
  become_user: "{{ __working_user }}"
  when: __rails_new and not rails_new_gemfile.stat.exists

スケルトン用のGemfile作って、指定のバージョンのrailsを追記します。

roles/ruby/tasks/main.yml
- name: rails new
  shell: |
    /bin/bash -lc "gem install bundler"
    /bin/bash -lc "rbenv rehash"
    /bin/bash -lc "bundle install --path vendor/bundle --jobs=4"
    /bin/bash -lc "bundle exec rails new -f {{ __app_dir }}"
  args:
    chdir: "{{ __app_dir }}"
  become_user: "{{ __working_user }}"
  when: __rails_new and not rails_new_gemfile.stat.exists

rails newします。

roles/ruby/tasks/main.yml
- name: bundle install (if app already exists)
  shell: |
    /bin/bash -lc "bundle install"
  args:
    chdir: "{{ __app_dir }}"
  become_user: "{{ __working_user }}"
  when: current_ruby_version.stdout != __version and not __rails_new

すでにRailsアプリをgit clone済み(マウント済み)でスケルトンを作らない場合、初回bundle installします。

Node.js

roles/nodejs/tasks/main.yml
- name: check if nodejs already installed
  stat:
    path: /usr/local/bin/node
  register: nodejs_bin

すでにインストール済みかチェック。

roles/nodejs/tasks/main.yml
- name: download and setup nodejs source
  shell: |
    curl -sL https://rpm.nodesource.com/setup_8.x | bash -
  when: not nodejs_bin.stat.exists

インストールされていない初期状態であれば、curlでソースを持ってきてビルドします。

roles/nodejs/tasks/main.yml
- name: install latest nodejs temporary
  yum:
    name: "{{ item }}"
    state: present
  with_items: 
    - nodejs
  when: not nodejs_bin.stat.exists

yum経由でインストールします。この時点では一時的なのでバージョンとかなんでもいいんです。

roles/nodejs/tasks/main.yml
- name: install n
  npm:
    name: n
    state: present
    global: yes
  when: not nodejs_bin.stat.exists

npmコマンドが使えるようになったので、バージョン管理のためのnというパッケージをインストールします。

roles/nodejs/tasks/main.yml
- name: install version specific nodejs using n
  shell: |
    n {{ __version }}
  when: not nodejs_bin.stat.exists

このnを使って好きなバージョンをインストールしちゃいましょう。

roles/nodejs/tasks/main.yml
- name: set global npm path
  template:
    src: 201_nodejs.sh
    dest: ~/.bash.d/201_nodejs.sh
    mode: 0755
  become_user: "{{ __working_user }}"
  when: not nodejs_bin.stat.exists

NODE_PATHをセットする設定を.bash_profileに追加します。(直接追加してるわけではないけど)
中身はこれです。

roles/nodejs/templates/201_nodejs.sh
export NODE_PATH=$(npm root -g)
roles/nodejs/tasks/main.yml
- name: install yarn
  npm:
    name: yarn
    state: present
    global: yes

yarnよく使うので入れちゃいます。

Redis

roles/redis/tasks/main.yml
- name: check if source already downloaded
  stat:
    path: /tmp/redis-{{ __version }}
  register: redis_source

インストール済みかチェックする。

roles/redis/tasks/main.yml
- name: download and unarchive source
  unarchive:
    src: http://download.redis.io/releases/redis-{{ __version }}.tar.gz
    dest: /tmp
    copy: no
  when: not redis_source.stat.exists

まだインストールしてなかったらソース持ってきて解凍する。unarchiveモジュール便利!

roles/redis/tasks/main.yml
- name: make and install
  make:
    chdir: /tmp/redis-{{ __version }}
    target: install
  when: not redis_source.stat.exists

make, make installもモジュールでできちゃいます。

roles/redis/tasks/main.yml
- name: check if binary source already deployed
  stat:
    path: /usr/local/bin/redis-server
  register: redis_server_bin_check

redis-serverコマンドにパスが通ってるかチェックする。

roles/redis/tasks/main.yml
- name: deploy binary source
  shell: |
    cp redis-server redis-cli redis-sentinel redis-benchmark redis-check-rdb redis-check-aof /usr/local/bin
  args:
    chdir: /tmp/redis-{{ __version }}/src
  when: not redis_server_bin.stat.exists

通ってなかったら諸々パス通します。

roles/redis/tasks/main.yml
- name: check if config file deployed
  stat:
    path: /etc/redis/{{ __port }}.conf
  register: redis_conf

設定ファイル配置済みかチェックする。

roles/redis/tasks/main.yml
- name: make config directory
  file: 
    path: /etc/redis
    state: directory
    mode: 0755

redisの設定ファイル置き場作る。

roles/redis/tasks/main.yml
- name: deploy config file
  shell: |
    mv redis.conf /etc/redis/{{ __port }}.conf
  args:
    chdir: /tmp/redis-{{ __version }}
  when: not redis_conf.stat.exists

設定ファイル置く。/etc/redis/ポート番号.confという形にしてる。

roles/redis/tasks/main.yml
- name: edit config file
  lineinfile:
    path: /etc/redis/{{ __port }}.conf
    regexp: "{{ item.regexp }}"
    line: "{{ item.line }}"
    state: present
  with_items:
    - {regexp: "^dir ", line: "dir {{ __data_dir }}"}
    - {regexp: "^logfile ", line: "logfile {{ __log_dir }}/{{ __port }}.conf"}
    - {regexp: "^loglevel ", line: "loglevel {{ __log_level }}"}
    - {regexp: "^daemonize ", line: "daemonize yes"}
    - {regexp: "^port ", line: "port {{ __port }}"}
    - {regexp: "^supervised ", line: "supervised systemd"}

設定ファイルまとめて書き換えてます。テンプレート化してもいいんだけど、バージョンによってどこまで差分出てくるかよく分からないので。。。

roles/redis/tasks/main.yml
- name: create usergroup for redis daemon
  group:
    name: redis
    state: present
    system: yes

- name: create user for redis daemon
  user:
    name: redis
    group: redis
    state: present
    createhome: no
    system: yes

redis用のグループとユーザー作ります。

roles/redis/tasks/main.yml
- name: create data and log directory
  file:
    path: "{{ item }}"
    state: directory
    owner: redis
    group: redis
    mode: 0755
  with_items:
    - "{{ __data_dir }}"
    - "{{ __log_dir }}"

データとログの置き場作ります。

roles/redis/tasks/main.yml
- name: check if service already daemonized
  stat:
    path: /etc/systemd/system/redis.service
  register: redis_service

デーモン化されているかチェックする。

roles/redis/tasks/main.yml
- name: daemonize service
  template:
    src: redis.service.j2
    dest: /etc/systemd/system/redis.service
    owner: root
    group: root
    mode: 0755
  when: not redis_service.stat.exists

デーモン化してsystemdに管理させます。
テンプレートはこんな感じ。

roles/redis/templates/redis.service.j2
[Unit]
Description=Redis

[Service]
Type=notify
ExecStart=/usr/local/bin/redis-server /etc/redis/{{ __port }}.conf
ExecStop=/usr/local/bin/redis-cli -p {{ __port }} shutdown
User=redis
Group=redis

[Install]
WantedBy=multi-user.target
roles/redis/tasks/main.yml
- name: reload daemon
  shell: |
    systemctl daemon-reload
  notify: start and enable server
  when: not redis_service.stat.exists

再起動して終了。

Elasticsearch

roles/elasticsearch/tasks/main.yml
- name: install JDK-1.8.0
  yum:
    name: "{{ item }}"
    state: present
    enablerepo: "*debug*"
  with_items:
    - java-1.8.0-openjdk-devel
    - java-1.8.0-openjdk-debuginfo

JDKをインストールします。

roles/elasticsearch/tasks/main.yml
- name: deploy yum repository info 
  template:
    src: elasticsearch.repo
    dest: /etc/yum.repos.d/elasticsearch.repo
    mode: 0644

Elasticsearchのリポジトリを登録します。
中身はこんな感じ。

roles/elasticsearch/templates/elasticsearch.repo
[elasticsearch-5.x]
name=Elasticsearch repository for 5.x packages
baseurl=https://artifacts.elastic.co/packages/5.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md

他のバージョン使いたいときは適宜作ってくださいますよう。

roles/elasticsearch/tasks/main.yml
- name: install 
  yum:
    name: elasticsearch-{{ __version }}
    state: present

指定のバージョンをインストールします。

roles/elasticsearch/tasks/main.yml
- name: check if kuromoji plugin already installed
  stat:
    path: /usr/share/elasticsearch/plugins/analysis-kuromoji
  register: kuromoji_check

kuromojiプラグインがインストール済みかチェックします。yumとかで入れるわけではないのでこの辺は自前で冪等性を担保する必要があります。

roles/elasticsearch/tasks/main.yml
- name: install kuromoji plugin
  shell: |
    ./elasticsearch-plugin install analysis-kuromoji
  args:
    chdir: /usr/share/elasticsearch/bin/
  when: not kuromoji_check.stat.exists

kuromojiプラグインのインストール。

roles/elasticsearch/tasks/main.yml
- name: edit  elasticsearch.yml
  lineinfile:
    path: /etc/elasticsearch/elasticsearch.yml
    regexp: "{{ item.regexp }}"
    line: "{{ item.line }}"
  with_items:
    - { regexp: "^http.port: ", line: "http.port: {{ __port }}" }
    - { regexp: "^bootstrap.memory_lock: ", line: "bootstrap.memory_lock: true" }
    - { regexp: "^path.logs: ", line: "path.logs: /var/log/elasticsearch" }
    - { regexp: "^network.host: ", line: "network.host: 127.0.0.1" }

設定ファイルをよしなに書き換えます。

roles/elasticsearch/tasks/main.yml
- name: unlock memory limit for elasticsearch user
  lineinfile:
    path: /etc/security/limits.conf
    regexp: "{{ item.regexp }}"
    line: "{{ item.line }}"
  with_items:
    - { regexp: "^elasticsearch soft memlock unlimited", line: "elasticsearch soft memlock unlimited" }
    - { regexp: "^elasticsearch hard memlock unlimited", line: "elasticsearch hard memlock unlimited" }

- name: add LimitMEMLOCK option for elasticsearch.service
  lineinfile:
    path: /usr/lib/systemd/system/elasticsearch.service
    regexp: "^LimitMEMLOCK"
    line: "LimitMEMLOCK = infinity"

Elasticsearchがメモリを最大限使えるように解放します。

roles/elasticsearch/tasks/main.yml
- name: edit  jvm.options
  lineinfile:
    path: /etc/elasticsearch/jvm.options
    regexp: "{{ item.regexp }}"
    line: "{{ item.line }}"
  with_items:
    - { regexp: "^-Xms", line: "-Xms{{ __jvm_xms }}" }
    - { regexp: "^-Xmx", line: "-Xmx{{ __jvm_xmx }}" }
  notify: start and enable service

jvmのヒープサイズを引き下げます。スペックにもよりますが、多分大半の場合メモリ不足になってElasticsearchが起動できなくなります。自分のマシンのスペックに応じて色々と変えてみてください。

以上でした

他によく使うミドルウェアってなんですかねー。
次はLAMP+Laravelでも作ろうかなー。

24
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
30