用途別Tips

Last-modified: Sun, 21 Oct 2018 01:22:36 JST (2015d)
Top > 用途別Tips

ファイル、ディレクトリ操作

ディレクトリ作成

- name: make apache log directory
  file:
    path: /var/log/httpd/localhost.localdomain
    state: directory
    owner: root
    group: root
    mode: 0755

ディレクトリ削除

- name: /var/www/htmlを削除
  file:
    path: /var/www/html
    state: absent

ファイル作成(中身は空っぽ)

- name: create timezone file
  file:
    dest: /etc/timezone
    state: touch

ファイル削除

- name: Remove default vhost
  file:
    dest: /etc/httpd/conf.d/welcome.conf
    state: absent
  • ファイルのコピーには、copyモジュールを使います。ただし、サーバ内のコピーではなく、Ansibleを実行しているローカルー>リモート間でのコピーになります。逆方向の場合はsynchronizeモジュールを使います。要するにSCPしています。サーバ内部でのコピーや移動はcommandモジュールでコマンドを叩くしかない?
    srcには、role/filesからの相対パスになります。

ファイルをコピー

- name: add authorized key
  copy:
    src: public_keys/test
    dest: /home/test/.ssh/authorized_keys
    mode: 0600
    owner: test
    group: test

リモートのファイルをダウンロードする

  • リモートのファイルをダウンロードしたり、同期をとったりする場合、synchronizeモジュールを使います。
    モジュール名の通り、内部的にはrsyncです。

リモートにあるファイルをダウンロードする。

- name: ファイルをダウンロードする
  synchronize:
    mode: pull
    src: /tmp/data.tar.gz 
    dest: ./tmp

中身チェック

  • 単純にファイルの中にある文字列が無ければ、何か条件発動する場合、こんな感じでできます。
    - name: 設定に値があるか?
      shell: grep "some_key_string" /foo/var/some_app.ini
      register: app_settings
      changed_when: false
    
    - name: 設定が必要か?
      set_fact:
        need_to_set: "{{ app_settings.stdout_lines.count < 1 }}"

存在チェック

  • ファイルやディレクトリの存在チェックには、statモジュールを使います。
    これは、registerと組み合わせる事で、条件分岐に使えます。

デフォルトのバーチャルホスト設定を無効化する。

- name: welcome設定存在チェック
  stat: path=/etc/httpd/conf.d/welcome.conf
  register: welcome_stat
- name: デフォルトのバーチャルホスト設定削除
  command: mv /etc/httpd/conf.d/welcome.conf /etc/httpd/conf.d/welcome.conf.disable
  when: welcome_stat.stat.exists
  notify:
    - "restart apache"

ファイルチェックに時間がかかる場合

statモジュールはファイルのハッシュをとって変更されたかをチェックしているようです。
従って、巨大なファイルの存在チェックをした場合、結構時間がかかることがあります。
その場合は以下のようにハッシュ計算をしないようにします。

- name: スワップファイル存在チェック(遅いのでチェックサムを返さない)
  stat:
    path: /swap.file
    get_checksum: no
    get_md5: no
  register: swapfile_exists

ユーザ操作

ユーザ作成

パスワードは以下のコマンドで生成

python -c 'import crypt; print crypt.crypt("hogehoge", "$1$SomeSalt$")'
- name: create user test
  user:
    name: test
    password: "$1$SomeSalt$Drh7s/vUcl5XnIZ/Neglz1"
    state: present

Cron設定

Cron作成

- name: add cron
cron:
  name: "test cron"
  user: root
  minute: "0"
  hour: "6"
  job: "ls -lah > /tmp/list"
  state: present

ファイルをダウンロードする

ファイルをダウンロードする。

- name: Jenkinsリポジトリの取得
  get_url:
    url: http://pkg.jenkins-ci.org/redhat/jenkins.repo 
    dest: /etc/yum.repos.d/jenkins.repo

ファイルのダウンロード時チェックサムをチェックする。

- name: mod_ruidをダウンロード
  get_url: 
    url: http://jaist.dl.sourceforge.net/project/mod-ruid/mod_ruid2/mod_ruid2-0.9.8.tar.bz2
    sha256sum: f8a178daf3bccf86e7e50e3224efc52165200470dece7b701466c5fbf1944b19
    dest: /tmp/mod_ruid2-0.9.8.tar.bz2
    force: True

環境設定

ホスト名設定

- name: ホスト名設定
  hostname:
    name: "example.com"

fstabへ追加

- name: スワップファイル永続化
  mount:
    name: swap
    src: /swap.file
    fstype: swap
    opts: defaults
    passno: '0'
    dump: '0'
    state: present

タイムゾーン設定

- name: タイムゾーンをJSTにする
  timezone:
    name: Asia/Tokyo

yum関連

yumでインストールする。

- name: Javaのインストール
  yum: name={{ item }} state=installed
  with_items:
     - java-1.7.0-openjdk
     - httpd

なお、nameの引数にサーバ内のrpmファイルパスを渡せば、ローカルのrpmをインストールすることが、URLを渡せば直接インストールすることができます。

yum updateをする。

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

rpmキーをインポート

- name: Jenkinsリポジトリのキーをインポート
  rpm_key:
    key: http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key

リポジトリを追加

- name: Add repository
 yum_repository:
   name: epel
   description: EPEL YUM repo
   baseurl: http://download.fedoraproject.org/pub/epel/$releasever/$basearch/
   enabled: no

テンプレート関連

  • テンプレート関連モジュールとして、templateモジュールがあります。
    テンプレートにはJinja2が使われます。
    srcのパスは、role/templatesからの相対パスになります。

テンプレートファイルを展開し、リモートに配置する。

- name: template vhost.conf
  template: 
    src: etc/virtual.conf 
    dest: /etc/httpd/conf.d/virtual.conf
    mode: 0644
    owner: root
    group: root
    notify: "restart apache"

サービス関連

サービスを起動し、自動起動をセットする。

- name: Jenkins Start
  service: 
    name: jenkins 
    state: running
    enabled: yes 

サービスを再起動する。(ハンドラでよく使う)

- name: "restart syslogd"
  service: name=rsyslog
  state: restarted

コマンドの実行関連

  • コマンドを実行するモジュールとして、commandモジュールshellモジュールがあります。
    commandモジュールではパイプやリダイレクトなどが使えなかったりするので、そのような場合はshellモジュールを使います。
    なお、冪等性の確保は各自で行わないといけないので注意です。

/usr/local/srcへディレクトリを移動した後ファイルを解凍

- name: ファイルを解凍
  command: tar -xjf /tmp/mod_ruid2-0.9.8.tar.bz2
  args:
    chdir: /usr/local/src/

ディレクトリのみパーミッション変更

- name: ディレクトリのみ権限調整
  shell: find . -type d -print | xargs chmod 755
  args:
    chdir: /var/www/html/

サーバ内部でファイルのコピー

- name: タイムゾーンをJSTにする
  shell: cp /usr/share/zoneinfo/Japan /etc/localtime

ファイル編集関連

  • ファイル編集関連としては、一行編集するlineinfileモジュールreplaceモジュールがありますが、
    あまり凝ったことができないので、テンプレートを使って丸ごと置き換えるか、shellモジュールでawkなど自力で頑張るしかなさそうです。
    なお、lineinfileモジュールは存在しないファイルを指定するとエラーになります。

sudoeas.dに追加する。

元ネタはここ

- name: add users to sudoers
      lineinfile: dest=/etc/sudoers.d/{{ item.user }}
        owner=root group=root mode=0440
        state=present
        create=yes
        line="{{ item.user }} {{ item.role }}"
        validate='visudo -cf %s'
      with_items:
       - { user: "user1", role: "ALL=(ALL) NOPASSWD: ALL" }

正規表現に一致する行を編集する。

- name: "php.iniの修正"
  lineinfile:
    dest: /etc/php.ini
    state: present
    backrefs: yes
    regexp: '{{ item.regexp }}'
    line: '{{ item.line }}'
  with_items:
    - regexp: '^;date.timezone ='
      line: 'date.timezone=Asia/Tokyo'
    - regexp: '^;mbstring.language = Japanese'
      line: 'mbstring.language = Japanese'
  notify:
    - "restart apache"

空のファイルを作成し、最後の行に追記する。(insertafter:EOFはデフォなので省略可能)

- name: テスト用html作成
  file:
    dest: /var/www/html/index.html
    state: touch
    owner: apache
    group: apache
- name: テスト用html作成
  lineinfile:
    dest: /var/www/html/index.html
    line: "testweb"
    insertafter: EOF
    owner: apache
    group: apache

リポジトリを無効化する。

#ただし、該当するものすべてが置換されるので注意
- name: DisabeRepo
  replace: 
    dest: /etc/yum.repos.d/{{item}}
    regexp: "enabled=1" 
    replace: "enabled=0"
  with_items:
    - epel.repo
    - remi.repo

Apache関連

Apacheのモジュールを有効化

- apache2_module:
    state: present
    name: php

BASIC認証用にhtpasswdファイル作成

- htpasswd:
    path: /etc/httpd/htpasswd_basic_user
    name: basic_user
    password: testpass

タスクの実行結果を保持したい

  • タスクの実行結果を変数に保持して、次のタスク実行を制御したい時があります。
    そのような場合は、registerを使い、結果を変数に保存し、次のタスクのwhenで条件分岐させます。
    例えば、shellモジュールでファイルをリネームする時、1回目はファイルが存在するので良いのですが、2回目以降は対象となるファイルが存在しないのでエラーになります。
    ファイルの存在チェックを行い、ファイルが存在したらshellタスクを実行する。というようにします。具体例はデフォルトのバーチャルホスト設定を無効化する。を見てください。

変数を作成する。

  • タスク実行時、複数の条件で判定させたい時があります。whenだと一つしか条件が指定できないので、registerでそれぞれの結果を保持し、それをset_factモジュールを用いて、条件判定後の結果を変数にセットします。

Apacheのmod_ruid2をインストール

- name: mod_ruidをダウンロード
  get_url:
    url: http://jaist.dl.sourceforge.net/project/mod-ruid/mod_ruid2/mod_ruid2-0.9.8.tar.bz2
    sha256sum: f8a178daf3bccf86e7e50e3224efc52165200470dece7b701466c5fbf1944b19
    dest: /tmp/mod_ruid2-0.9.8.tar.bz2
    force: True
  register: downloaded_tarball
- name: 既にバイナリが無いかチェック
  stat: path=/usr/lib64/httpd/modules/mod_ruid2.so
  register: st
- name: コンパイルが必要かどうか?
  set_fact:
    need_to_build: "{{ st.stat.exists == False or st.stat.size == 0 or
                       downloaded_tarball | changed }}"
- name: ファイルを解凍
  command: tar -xjf /tmp/mod_ruid2-0.9.8.tar.bz2 -C /usr/local/src/
  when: need_to_build
- name: compile&install mod_ruid2
  command: apxs -a -i -l cap -c mod_ruid2.c chdir=/usr/local/src/mod_ruid2-0.9.8
  when: need_to_build

インベントリファイルについて

  • インベントリファイルでグループ共通変数を使えると便利なことがあります。
    例えば、webserverとstagingserverというグループがあるとします。
    これらは、group_versでそれぞれ独立して定義できますが、例えば共通のAPIサーバにアクセスするなどの場合、同じ定義をそれぞれしないといけません。その場合、次のように共通のグループを作ってあげるとよい。
    [webserver]
    192.168.0.xxx 以下省略
    [stagingserver:
    192.168.1.xxx 以下省略
    
    # グループ共通変数を読み込む
    [common_vars:children]
    webserver
    stagingserver
    こうすると、common_varsという名前でgroup_varsの中にファイルを作成すると、どちらのグループからも参照することができる。

MySQL関連

  • MySQL関連として、mysql_dbモジュールmysql_userモジュールがあります。
    ただし、このモジュールを使用する場合は、リモートサーバ側にMySQLのPythonライブラリ(MySQL-Python)やMySQLクライアントが必要になります。
    Pythonライブラリをインストールする際は、リモートサーバ上のPythonバージョンを確認したうえで、適切なバージョンのライブラリをインストールしましょう。参考

MySQL5.7+mysqlsecureinstallation

元ネタはここ

- set_fact:
    mysql_log_file: /var/log/mysqld.log

- name: 初回起動時か判定する為にログファイルの存在チェック
  stat:
    path: "{{ mysql_log_file }}"
  register: log_file
  changed_when: false

- name: MySQL5.7起動
  service:
    name: mysqld
    state: started
    enabled: yes

- name: 一時パスワード取得
  shell: cat {{ mysql_log_file }} | grep 'temporary password'
  register: temporary_password
  changed_when: false
  when: log_file.stat.size == 0

- set_fact:
    mysql_temp_password: "{{ temporary_password.stdout.split(' ')[-1] }}"
  when: log_file.stat.size == 0

#MySQL5.7はパスワードポリシーが厳しいので、自動生成されたパスワードでパスワードを再設定して、SecureInstallationを実行する。
- name: 一時的にパスワード変更
  command: >
    mysqladmin password '{{ mysql_temp_password }}' -u root -p'{{ mysql_temp_password }}'
  changed_when: false
  when: log_file.stat.size == 0

- name: MySQLSecureInstallation実行
  command: mysql_secure_installation -u root -p'{{ mysql_temp_password }}' -D
  changed_when: false
  when: log_file.stat.size == 0
#その後Rootのパスワードを設定する
- name: PWポリシーを一時的に緩める
  shell: |
    mysql -u root -p'{{ mysql_temp_password }}' --connect-expired-password -e "SET GLOBAL validate_password_l ength=8;"
    mysql -u root -p'{{ mysql_temp_password }}' --connect-expired-password -e "SET GLOBAL validate_password_policy=LOW;"
  changed_when: false
  when: log_file.stat.size == 0

- name: MySQLのRootパスワード変更
  command: >
    mysqladmin password '{{ mysql_root_password }}' -u root -p'{{ mysql_temp_password }}'
  changed_when: false
  when: log_file.stat.size == 0

データベースの作成

- name: Create database
  mysql_db: 
    db: '{{ mysql_dbname }}' 
    state: present 
    encoding: utf8
    login_user: root
    login_password: '{{ mysql_root_password }}'

ユーザの作成

- name: Create database user
  mysql_user: 
    name={{ mysql_dbuser }}
    password="{{ mysql_dbpass }}"
    priv={{ mysql_dbname }}.*:ALL
    state=present
    host=%
    login_user=root
    login_password='{{ mysql_root_password }}'

DMPのインポート

- name: DBのインポート
  mysql_db:
    name : "database_name"
    state: import
    target: /tmp/all_data.sql #リモート上のパスなので注意
    login_user: "db_admin"
    login_password: "hogehoge"
    login_host: "localhost"

PostgreSQL関連

  • PostgreSQL関連モジュールとして、postgresql_dbモジュールpostgresql_userモジュールがあります。
    ただし、このモジュールを使用する場合は、リモートサーバ側にPostgreSQLのPythonライブラリ(python-psycopg2)やPostgreSQLクライアントが必要になります。
    なお、初回インストール時のinitdbや、接続に必要な設定(postgresql.confのlisten_addressesとかpg_hba.confとか)は設定してくれないので、事前に設定しないと動きません。

データベースの作成

- name : データベース作成
  postgresql_db: 
    name: "{{ pgsql_dbname }}"
    encoding: "UTF-8"
    login_user: postgres
  sudo_user: postgres
  sudo: yes

ユーザ作成

#NOSUPERUSERをつけるとコケる。https://groups.google.com/forum/#!topic/ansible-project/-tDBSPtByfM
- name: ユーザ作成
  postgresql_user: 
    db: "{{ pgsql_dbname }}"
    name: "{{ pgsql_dbuser }}"
    password: "{{ pgsql_dbpass }}"
    priv: ALL
    encrypted: yes
    state: present
    login_user: postgres
    role_attr_flags: "NOCREATEDB,NOCREATEUSER,NOCREATEROLE"
  sudo_user: postgres
  sudo: yes

Zabbix関連

ZabbixServerへAgentの登録

元ネタはここ
Ansibleのモジュールはかなり高機能で、使い勝手が良い。

- name: ZabbixServerへ登録
  zabbix_host:
    server_url: http://zabbix_server.example.com
    login_user: "{{ zabbix_server_admin_id }}"
    login_password: "{{ zabbix_server_admin_password }}"
    host_name: "{{ host_name }}"
    host_groups:
      - Linux servers
    link_templates:
      - "Template OS Linux"
    status: enabled
    state: present
    interfaces:
      - type: 1
        main: 1
        useip: 1
        ip: "{{ webserver_ip }}"
        dns: ""
        port: 10050

その他

タスクの失敗を無視したい

  • タスクを実行する時、エラーになっても続行したい場合、ignore_errorsをTrueにすると、失敗しても無視されます。

MySQLのテストデータベースを削除

# RootのPW変更後実行するとエラーになるので、エラーを無視するようにしている。
- name: remove the MySQL test database
  action: mysql_db db=test state=absent login_user=root login_password=''
  ignore_errors: True

テンプレートファイル内で{}が使いたい。

  • シェルスクリプトなど{}をつかうファイルをJinja2テンプレートで作成すると、文法エラーが出て困ることがあります。
    そういうときは、適用させたくないブロックを{% raw %}、{% endraw %}で囲むと、テンプレートが適用されません。
    #!/bin/bash
    
    backup_dir=/var/www/html
    
    {% raw %}
    cd ${backup_dir}
    
    datetime=`date +%Y%m%d%H%M`
    backup_file=/backup/backup_${datetime}.tar.gz
    tar cvzfp ${backup_file} ./
    {% endraw %}

Ubuntuなどでsudoする際にパスワード入力で止まる。

  • ubuntuなどで、sudoする際にパスワード入力が出て止まることがあります。
    まぁちゃんとsudoの設定をすればいいのですが、その作業はAnsibleで出来ないという事になるので、出来ればスクリプト化したいですよね。
    その場合、セキュリティ的には微妙になるのですが、group_varsに
    ansible_sudo_pass: p@ssw0rd
    のようにパスワードを書くと自動入力されます。
    間違ってもgitとかで公開しちゃだめだぞ!!

Ansible実行時、UTF8じゃないと怒られる。

  • Ansible実行時、以下のようなメッセージが出ることがあります。
    ERROR! Unexpected Exception: 'utf8' codec can't decode byte 0x8a in position 82: invalid start byte
    これば、PlayBookがUTF-8じゃないときに出ますので、UTF-8で保存しましょう。
    特に日本語でコメント入れていたりすると結構な頻度で発生します(汗
    ググってもPythonの問題が出てきたりと割とはまります・・・

Ansible実行時、いちいちKnownHostsに登録するか聞いてくるのが面倒

  • SSH接続を公開鍵認証しているとき、カギを作り直したりサーバを作り直したときいちいち登録するか聞いてくるのが面倒になります。
    その時は、Ansibleの実行ユーザにて、以下の設定をすると聞いてこなくなります。
    vi ~/.ansible.cfg
    
    [default]
    host_key_checking = False

複数のタスクでループを回したい

元ネタは ここ なんですが、
Ansibleで複数タスクをループで回したい時があります。
Ansibleでループと言えばwith_itemsですね。

- name: データベース作成
  mysql_db:
    db: "{{ item }}"
    state: present
    encoding: utf8
    login_user: root
    login_password: '{{ mysql_root_password }}'
  with_items:
    - hoge_db
    - hugga_db

- name: データベースユーザ作成
  mysql_user: 
    name: "{{ item.user }}"
    password: '{{ item.pass }}'
    priv: "{{ item.db }}.*:ALL"
    state: present
    host: "%"
    login_user: root
    login_password: '{{ mysql_root_password }}'
  with_items:
    - db: hoge_db
      user: hoge_user
      pass: huga_pass
    - db: huga_db
      user: huga_user
      pass: huga_pass

うん。とっても冗長です。
この二つが同時に回せると便利ですね。
これ、Ansible2系から 使える らしいのですが、クラスメソッドさんのブログにあるように、include単位でwith_itemsが使えます。
ついでに変数にまとめましょう。

  • common_vers/all.yml
    mysql_root_password: Do̲you̲love̲MySQL57?
    mysql_databases:
      - mysql_dbname: hoge_db
        mysql_dbuser: hoge_user
        mysql_dbpass: hoge_pass
      - mysql_dbname: huga_db
        mysql_dbuser: huga_user
        mysql_dbpass: huga_pass
  • roles/mysql/tasks/main.yml
    # DBを複数作成する
    - include: create_resources.yml
      with_items:
        - "{{ mysql_databases }}"
      loop_control:
        loop_var: resources
  • roles/mysql/tasks/create_resources.yml
    # データベース作成
    - name: データベース作成
      mysql_db:
        db: "{{ resources.mysql_dbname }}"
        state: present
        encoding: utf8
        login_user: root
        login_password: '{{ mysql_root_password }}'
    
    - name: データベースユーザ作成
      mysql_user: 
        name: "{{ resources.mysql_dbuser }}"
        password: '{{ resources.mysql_dbpass }}'
        priv: "{{ resources.mysql_dbname }}.*:ALL"
       state: present
        host: "%"
        login_user: root
        login_password: '{{ mysql_root_password }}'

インベントリファイル(hosts)やグループ変数(common_varsやhosts_varsなど)が編集しづらい

実はこれらのファイルは、
hosts -> hosts.ini
common_vars -> common_vars.yml
と拡張子をつけても問題ありません。(もちろん実行時に拡張子を含めて指定する必要はありますが)
エディタの関連付けが動かなくて面倒な時にちょっとしたTipsです。

インベントリパターンで、インベントリファイルの直下以外のロールを読み込みたい

Ansibleはディレクトリ構成が自由にできるのですが、一応ベストプラクティスな構成があります。

ここにある、詳細ステージパターンや、ここにあるような、ほかのサーバで利用出るロールを共通にしたいけど、サーバ毎にロールをコピペするのは避けたい場合、
環境ごとにインベントリファイルのディレクトリを分けて、

ansible-playbook ./inventries/staging/main.yml -i ./inventries/staging/hosts.ini

のような感じで動かします。
Ansibleはデフォルトでインベントリファイルの直下にあるroleディレクトリからロールを探しに行きます。
その場合、rolesに相対パスで書くのですが、何故かそんなロールはないと怒られてしまいました。
その場合、

- name: staging
  hosts: staging
  become: yes
  vars_files:
    - ../group_vars/common_vars.yml
    - ./group_vars/sites.yml
  roles:
    - { role: ../roles/common }
    - { role: ../roles/mysql }
    - { role: ./staging }

のようにするとうまくいきました。Ansibleのバージョンによるのかもしれません。


Counter: 543, today: 2, yesterday: 0

このページの参照回数は、543です。