ENGINEER BLOG

ENGINEER BLOG

Ansible入門

お久しぶりです。
ソリューションC&I本部3年目の新井です。
前回初めて記事を書いてから早1年半が経ってしまいました...
(前に書いた記事はこちら!)

とあるサービスのリプレイスプロジェクトに参画したのですが、
そこで上がったAnsibleでの構築の話。
初めてのことに対してなかなか手が出ない個人的な課題もあり、これを機にチャレンジしてみよう...
ということで、プロジェクトで使えるようになるためにAnsibleを勉強した時の話をしたいと思います!

Ansibleについて

AnsibleはIaC(Infrastructure as Code)を実現するための構成管理ツールの一つで、
YAML型式で設定ファイルを記述することにより、インフラ構築を自動的に実行することができます。
また、構築を自動化するだけでなく、設定ファイルで管理することにより
どの環境にどんな設定がされているのか、パラメータシート的な役割も担うことができます(個人的見解)。

Ansibleを利用する場合、ちょっとした準備が必要になります。
Ansibleの実行環境を「実行端末」、Ansibleを利用して構築する環境を「ターゲット端末」とすると、
実行端末には、AnsibleとPythonのパッケージが必要となります。
また、ターゲット端末に準備するものは無いのですが、ターゲット端末がリモートである場合、
実行端末とターゲット端末がssh接続できることが前提となります。
簡単なイメージはこちら↓
ansible_0

準備...

インストール

前章で話した通り、Ansibleを利用するには、AnsibleとPythonのパッケージインストールが必要です。
私はこちらのサイトを参考にインストールを実施しました。
ちなみに私の実行環境はWSLのUbuntuを利用してます。
今回Ansibleを実行している環境はこちら↓です。

OS:Ubuntu(WSL)
Ansible:2.6.9
Python:2.7.17

環境セッティング

YAMLなどの設定ファイルの配置、階層は以下のような感じにしています。

親ディレクトリ
  ├ playbook関係のYAMLファイル
  ├ hosts_vars
  |  ┕ サーバ別パラメータ用YAMLファイル
  ├ group_vars
  |  ┕ グループ別パラメータ用YAMLファイル
  ├ inventory
  |  ┕ 接続サーバ情報用YAMLファイル
  ┕ roles
     ┕ test-setting-role
        ├ files
        |  ┕ サーバ毎のディレクトリ
        |     ┕ profile
        |         ┕ ユーザ毎のprofile
        ├ handlers
        |  ┕ main.yml
        ┕ tasks
           ┕ サーバ設定用YAMLファイル

Ansibleを触るのが初めてということもあり結構ごちゃごちゃしちゃいました...
全部を説明するのは厳しいので、以下に絞ってこれから説明していこうと思います。

- playbook関係のYAMLファイル
- rolesとtasks

playbook関係のYAMLファイル

Ansibleを動かす際、playbookというものを参照して処理をさせます。
今回、playbookには接続サーバ情報用YAMLファイルinventoryで定義した
どのホストに対して、どんな権限で、何のroleを実行するのかを記載しています。
例)

- hosts: hoge
  become: True
  roles:
    - hoge-setting-role

それぞれの意味ですが、

  • hosts :
    • inventory/接続サーバ情報用YAMLファイル内で設定したホスト名を定義
    • 定義したホストに対して処理を実行する
  • become :
    • Ture時は管理者権限で処理を実行する
  • roles :
    • roles/roleディレクトリを定義
    • 定義したrole内の処理を実行する

といった感じになります。
対象のplaybookを指定し実行することで、playbook内で定義している内容の処理を開始することができます。

rolesについて

rolesは、playbookで読み込むためのモジュールです。
前章を例にすると、playbook上でhoge-setting-roleを呼び出しています。
今回roles配下は、与えられた値から処理を実際に実行するtasksや、サーバに配置したいコンテンツを
保管するfiles(今回はfilesの説明は省きます...)などで構成しています。

tasksについて

tasksでは実際にどういった処理をさせるのかを定義します。
身近なものだと、ユーザを作ったり、yumでパッケージをインストールすることができます。
Ansibleにはモジュールというものが存在し、それらを用いることでtaskを定義することができます。
例えば、yumでhttpdをインストールしたいという場合は、↓のような感じで定義します。

- name: yum install for httpd
  yum:
    state: present
    name: httpd

各項目の説明です。

  • name(1行目) :
    • こちらはタスク名になります。実際に実行した際にログ上に出力されるのでどんな処理をしているのかわかりやすくするのがおすすめです。
  • yum :
    • これがモジュールになります。今回はyumモジュールを定義していますが、どんなモジュールがあるのかはAnsibleの公式ドキュメントなどいろんなところにあるので探してみてください!!
    • state,name :
      • yumモジュールで定義できる専用の変数みたいなものになります。
      • stateはどんなバージョンをインストールするかを設定できます。presentの場合は最新バージョンを意味します。
      • nameは何をインストールするかを設定できます。今回はhttpdを対象としています。

文章ばかりではわかりにくいので、ここで試しに実行してみましょう。
定義はこんなかんじ。

  • task-playbook.yml
- hosts: test_server
  become: True
  roles:
    - test-setting-role
  • inventory/hosts.yml
all:
  children:
    test:
      children:
        test_dev:
          children:
            test_dev_ap:
              hosts:
                test_server:
                  ansible_host: 対象サーバのグローバルIPアドレス
                  ansible_ssh_common_args: ''
  • roles/test-setting-role/tasks/main.yml
- name: Main controller for common setting
  debug:
    msg: "Start setting {{ inventory_hostname }}"

- name: Include tasks
  include_tasks:
    file: "{{ item }}"
  loop:
    - yum_test.yml

- name: Finished tasks
  debug:
    msg: "Completed {{ inventory_hostname }}"
  • roles/test-setting-role/tasks/yum_test.yml
- name: yum install
  yum:
    state: present
    name: httpd

playbookを実行すると、roles/test-setting-role/tasks配下にあるmain.ymlが実行されます。
main.ymlにそのままyumの処理を直書きしても良いのですが、処理をできる限り再利用できるように
分けるようにしたいと思います。
include_tasksモジュールを利用することで、他のtaskファイルを読み込み処理を実行することができます。

playbookの実行はansible-playbookコマンドでできます。

sudo ansible-playbook -i inventory/hosts.yml test-playbook.yml

-iでinventoryの指定、最後に実行するplaybookの指定をします。

実行すると...
ansible_1

たくさんログが出てきましたね...
主要な部分だけ説明すると
TASK [test-setting-role : yum install]が今回httpdをインストールしているタスクになります。
この部分でエラーが出ずにchangedのステータスになっているので、問題なくインストールができたことを意味しています。
最後のPLAY RECAPでもわかるようになっているので、そこで確認すればトータルで処理が問題なかったかがわかるようになっています。

対象サーバの確認もしてみましょう。
ansible_2

ちゃんとインストールされていましたね!

さて本番...Ansibleを動かしてみましょう

一通り基本的な動作が確認できたということで、
実際にリプレイスで使うように処理を組んでいきましょう...
全部説明するのはさすがに多いので、↓の5つに絞って説明していきます。
ファイルの中身だけ参考程度に載せたいと思います。

- main.yml
- hostname.yml
- yum.yml
- group.yml
- user.yml
  • roles/test-setting-role/tasks/main.yml
- name: Main controller for common setting
  debug:
    msg: "Start setting {{ inventory_hostname }}"

- name: Include tasks
  include_tasks:
    file: "{{ item }}"
  loop:
    - hostname.yml
    - yum.yml
    - group.yml
    - user.yml
    - kshrc.yml
    - localization.yml
    - sshd.yml
    - hosts.yml
    - sudoers.yml
    - services.yml
    - profile.yml
    - vimrc.yml
- name: Reboot host
  debug:
    msg: "Reboot {{ inventory_hostname }}"

- name: Reboot
  reboot:

- name: Finished tasks
  debug:
    msg: "Completed {{ inventory_hostname }}"

前章のmain.ymlを改良したものになります。
include_tasksモジュールとloopを組み合わせることにより
他タスクを繰り返して処理させています。
また、中には再起動しないと設定が反映されないものもあるので
rebootによりサーバの再起動を実施しています。
このモジュール、結構親切で、サーバが完全に上がり切るまで待ってくれます。

  • roles/test-setting-role/tasks/hostname.yml
- name: Setting hostname
  hostname:
    name: "{{ inventory_hostname }}"

hostnameモジュールはその名の通りホスト名を設定することができます。
nameにホスト名を記載することで、その内容で設定を行います。
{{ inventory_hostname }}はAnsibleの独自の変数で、inventory/host.ymlで指定した
ホスト名を表します。
今回の場合、test_server{{ inventory_hostname }}に代入されます。

  • roles/test-setting-role/tasks/yum.yml
- name: yum repository enable
  command: yum-config-manager --enable rhel-7-server-rhui-optional-rpms

- name: yum update
  yum:
    name: "*"
    state: latest

- name: yum install
  yum:
    state: present
    name: "{{ item }}"
  loop: "{{ yum_packages }}"

こちらも前章のものを改良したものになります。
yumモジュールで対象を"*"statelatestにすることで、yum updateを実施しています。
また、インストールも、対象のパッケージを変数に格納させループし、インストールするようにしています。

  • roles/test-setting-role/tasks/group.yml
- name: Create groups
  group:
    name: "{{ item.group }}"
    gid: "{{ item.gid }}"
  loop: "{{ group }}"

groupモジュールを利用することでgroupの作成をすることができます。
nameでグループ名、gidはグループIDを指定します。
ここでも同様に変数に格納したものをループで処理させるようにしています。

  • roles/test-setting-role/tasks/user.yml
- name: Create users
  user:
    name: "{{ item.user }}"
    uid: "{{ item.uid }}"
    group: "{{ item.group }}"
    password: "{{ item.password }}"
    createhome: yes
    home: "{{ item.home_directory }}"
    shell: "{{ item.shell }}"
  loop: "{{ user }}"

userモジュールを利用することでuserの作成をすることができます。
それぞれの設定値についてはこんな感じです。

  • name:ユーザ名
  • uid:ユーザID
  • group:所属グループ
  • password:ユーザのパスワード
  • createhome:ホームディレクトリを作成するかしないか(yesは作成し、noは作成しない)
  • home:ホームディレクトリの指定
  • shell:ログインシェルの指定

ここでも同様に変数に格納したものをループで処理させるようにしています。

今回のせている他の設定はこんな感じ↓

  • roles/test-setting-role/tasks/kshrc.yml
    • /etc/kshrcに追加したい設定をblock配下に記述
    • 独自のフラグで対象ユーザに~/.kshrcを配置
- name: Setting /etc/kshrc
  blockinfile:
    path: /etc/kshrc
    insertafter: 'unset -f pathmunge'
    block: |
      PS1="$"
      [ $(whoami) = 'root' ] && PS1="#"
      export PS1=[$(whoami)@$(hostname):'$PWD']$PS1" "
    marker: "# {mark} ANSIBLE MANAGED BLOCK {{ inventory_hostname }}"
    backup: yes

- name: Setting ~/.kshrc
  copy:
    dest: "{{ item.home_directory }}/.kshrc"
    owner: "{{ item.user }}"
    group: "{{ item.group }}"
    mode: "0644"
    content: |
      # .kshrc

      # Source global definitions
      if [ -f /etc/kshrc ]; then
        . /etc/kshrc
      fi

      # use emacs editing mode by default
      set -o emacs

      # User specific aliases and functions
  when: item.is_db2_user
  loop: "{{ user }}"
  • roles/test-setting-role/tasks/localization.yml
    • localeja_JP.UTF-8に設定
    • ユーザ毎のlocaleSHIFT_JISが設定できるように有効化
    • timezoneAsia/Tokyoに設定
- name: Setting locale
  command: localectl set-locale LANG=ja_JP.UTF-8

- name: Add locale sjis
  command: localedef -f SHIFT_JIS -i ja_JP /usr/lib/locale/ja_JP.sjis

- name: Setting timezone
  timezone:
    name: Asia/Tokyo
  • roles/test-setting-role/tasks/sshd.yml
    • パスワード認証でもssh接続できるように該当箇所の置換を実施
- name: Setting sshd
  lineinfile:
    path: /etc/ssh/sshd_config
    state: present
    backrefs: yes
    regexp: "^PasswordAuthentication no"
    line: "PasswordAuthentication yes"
    backup: yes
  notify: Restart sshd
  • roles/test-setting-role/tasks/hosts.yml
    • 変数に格納した内容を/etc/hostsの最後尾(EOF)へ追記
- name: Setting hosts
  blockinfile:
    path: /etc/hosts
    insertafter: EOF
    block: "{{ hosts }}"
    marker: "# {mark} ANSIBLE MANAGED BLOCK {{ inventory_hostname }}"
    backup: yes
  • roles/test-setting-role/tasks/sudoers.yml
    • 該当箇所の置換
    • 最後尾(EOF)に追記
- name: Comment out '%wheel'
  lineinfile:
    path: /etc/sudoers
    state: present
    backrefs: yes
    regexp: "^%wheel"
    line: "#%wheel"
    backup: yes

- name: Setting sudoers
  blockinfile:
    path: /etc/sudoers
    insertafter: EOF
    block: "{{ sudoers }}"
    marker: "# {mark} ANSIBLE MANAGED BLOCK {{ inventory_hostname }}"
    backup: yes
  • roles/test-setting-role/tasks/services.yml
    • 最後尾(EOF)に追記
- name: Setting services
  blockinfile:
    path: /etc/services
    insertafter: EOF
    block: "{{ services }}"
    marker: "# {mark} ANSIBLE MANAGED BLOCK {{ inventory_hostname }}"
    backup: yes
  • roles/test-setting-role/tasks/profile.yml
    • ちょっと複雑
    • copyモジュールにより、ローカルの該当ファイルを対象のサーバへ配置
    • 所有者も指定可能
- name: Copy ~/.profile
  copy:
    src: "roles/test-setting-role/files/{{ inventory_hostname }}/profile/{{ item.user }}/"
    dest: "{{ item.home_directory }}/"
    owner: "{{ item.user }}"
    group: "{{ item.group }}"
    mode: "0644"
  when: item.having_dot_profile
  loop: "{{ user }}"
  • roles/test-setting-role/tasks/vimrc.yml
    • 新しくファイル作ってblock配下の値を投入
    • 作成したユーザのうち、フラグに引っかかったもののみ処理を実施
- name: Setting vimrc
  blockinfile:
    path: "{{ item.home_directory }}/.vimrc"
    create: yes
    marker: ' " # {mark} ANSIBLE MANAGED BLOCK {{ inventory_hostname }}'
    block: |
      set encoding=sjis
      set fileencoding=cp932
      set termencoding=sjis
  when: item.is_db2_user
  loop: "{{ user }}"

長々失礼いたしました...
ひとまず上に記載した処理を実行していきましょう...!
ちなみに今回定義している変数はこちら↓

group:
  - group: test1
    gid: 300
  - group: test2
    gid: 301
  - group: test3
    gid: 302

user:
  - user: test1
    uid: 211
    group: test1
    password: $6$JSTTOTPp$nnOVC337kb/H6nFCrsY0w9ilsfWvo/lLR7x4k1EO3HryW4EEBsDNXpoN8JnDZ55b4BPRYtmagoFxEhd/U6l7c.
    home_directory: /home/test1
    shell: /bin/bash
    is_db2_user: true
    having_dot_profile: true
  - user: test2
    uid: 221
    group: test2
    password: $6$I87PfjsW$UK1ytI95/PAVBwd7qndU0Qldj5v5eJLwgQd7N0zmUHp/VMsj3SaSdgbqS4TulLqlB/PhaMZ6luxKSKYOgcIGa0
    home_directory: /home/test2
    shell: /usr/bin/ksh
    is_db2_user: true
    having_dot_profile: true
  - user: test3
    uid: 222
    group: test3
    password: $6$fOdqr3sb$jGrqtEOmS..zABkW5A6VLKd2ouiTueTc7Yc.N1ZQ2nIHYuLboVonlheclK9PmRpvwdvPGIwUU52pDTflUEYUx1
    home_directory: /home/test3
    shell: /usr/bin/ksh
    is_db2_user: true
    having_dot_profile: true


sudoers: |
  # Cmnd alias specification
  Cmnd_Alias TEST_BIN = /bin/rm, /usr/bin/sar, /usr/bin/topas, /usr/local/bin/nmon, /usr/bin/svmon, /usr/sbin/lsof, /usr/bin/filemon, /usr/bin/fileplace, /usr/bin/netpmon, /usr/bin/trcstop

  # User privilege specification
  test1 ALL=(ALL) NOPASSWD:MON_BIN, IHS_BIN, WAS_BIN, DMGR_BIN, USER_CMD, LOGROT
  test2 ALL=(ALL) NOPASSWD:MON_BIN

hosts: |
  10.126.100.101   test1
  10.126.100.102   test2
  10.126.100.103   test3
  10.126.100.104   test4
  10.102.100.105   test5
  10.126.100.106   test6

services: |
  # test      60000/tcp     # Ansible Test


yum_packages:
  - tomcat
  - mysql
  - httpd
  - java-1.8.0-openjdk.x86_64

ちゃんと実行できるかな...

ansible_3

途中までうまくいってたのにーーーーー!
どうやら、ローカルに配置してるファイルの場所が見つからずエラーになっている模様。。。
修正して再トライ!!

ansible_4

修正して問題なく設定が完了しました!
前回と少し違うのは、すでに設定完了しているタスクがchangedからokになっていること。
これはAnsibleの冪等性という性質からなっているもので、一度実行したものは二回目以降も
同じ結果になるというようになっています。
そのため、設定は変わらずそのままでOKですよーってなったということですね。

ちゃんと設定されている...?

さて、Ansibleの実行が完了しましたが、本当に設定されているのか...
実際にサーバに接続し確認していきましょう!
※全部は多いのでこちらもピックアップしてキャプチャ貼っていきますね...

  • host名
    ansible_5
  • グループ
    ansible_6
  • ユーザ
    ansible_7
  • locale,timzone
    ansible_8
  • yum
    ansible_9

ちゃんとできてそうですね!

最後に

本来であれば、プロジェクトで実際に使ったパラメータや、その時の挙動などを記事にできれば良いのですが、
公開できないものもありそうなので、今回の記事用にところどころ修正して実行しなおしました...

今回はAnsibleの基礎ということで、できるだけ身近な処理に絞り、設定ファイルやパラメータの説明をしてきましたが、実際のプロジェクトではNFSマウントやvsftpdの設定とかもしているので、Ansibleはもっといろいろなことができると思います。
実際にAnsibleを使ってみた個人的な感想としては
一度処理を書いてしまえば、後は変数を定義するだけで同じように実行できるのはとても便利だなと思いました。(その処理を書くのが結構大変なのですが...)
初めてAnsibleを触ってみたということもあり拙い記事とコードの紹介になってしまいましたが、もっと汎用性の高いものを作れるように深く勉強していきたいと思います!
せっかくなので次はterraformを使って、AWS環境の自動化もやってみたいですね!

以上、最後までお付き合いいただきありがとうございました!