Ansible for the Absolute Beginner 勉強メモ

はじめに

www.udemy.com

Ansible for the Absolute Beginner の勉強メモ。自分用

ansibleのきそ

「誰に」「何をするか」を規定する。

  • inventory ファイル(「誰に」)
    • 管理するサーバ達の接続先の設定を入れる。ansible版のhostsに近い。複数サーバひとまとめに扱える点が先進的
    • デフォルトでは/etc/ansible/hostsだが、-i <inventory name>で任意に指定可能
  • playbook(「何をする」)
    • サーバに対して何をするかを規定する
    • yamlファイル
    • taskが実際に実行するコマンド(=module)に相当
      • コマンド・スクリプトの実行
      • パッケージのインストール
      • 起動・停止・再起動
    • play
      • 一連のtaskをまとめたもの。
      • 実際に実行されるのはplay。task単位での実行はできない

例えば・・・

# inventory ファイル
localhost

server1.company.com
server2.company.com

[mail]
server3.company.com
server4.company.com

[db]
server5.company.com
server6.company.com
# httpdを立ち上げるplaybook
- 
  name: Play 1
  hosts: localhost  # inventoryファイルにlocalhostが記載されている必要あり。
  tasks: 
  - name: Execute command 'date'
    command: date
  - name: Execute script on server
    script: test_script.sh
- 
  name: Play 2
  hosts: localhosts
  tasks: 
  - name: Install httpd service
    yum: 
      name: httpd
      state: present
  - name: Start web server
    service: 
      name: httpd
      status: started
    
  • taskはリストなので、順序がある。上から順に実行される
# 全てのサーバにpingするplaybook
- 
  name: Test connectivity to target servers
  hosts: all
  tasks: 
    - name: Ping test
      ping: # pingモジュールには属性が定義されていないのでこれだけでOK

module

  • 上記のcommand, script, yum,service に相当するもの。
  • 例えばhttpdをインストールしたい場合、

    • 従来叩くようなコマンド(yum install httpd)を記載する代わりに、
    • 「どうあって欲しいか」を表現するyamlを書く。
tasks: 
- name: Install httpd service
  yum: 
    name: httpd
    state: present
  • moduleのyaml記法は別途redhatが定義しているため、残念ながら覚えるか調べる必要がある。

    • ansible-doc <module name>で調べられる。ansible-doc pingなど

playbookの実行

ansible-playbook playbook.yml # で実行 (インベントリファイルは/etc/ansible/hostsを利用)
ansible-playbook playbook.yml -i inventory.txt # インベントリファイルを明示的に指定したい場合
ansible-playbook --help # でヘルプ確認

ansibleの実行方法

  • ansibleコマンドを使うやり方と、ansible-playbookコマンドを使うやり方がある。
  • ansibleコマンドの方がプリミティブなコマンド。
    • 命令的記法。(kubectl run busybox --image=busybox と同じアイデア)
# ansibleコマンドの例
ansible <hosts> -a <command> -i inventory.txt
ansible all -a "/sbin/reboot" -i inventory.txt # allはいつでも利用可能。インベントリファイルに含まれる全てのサーバを表す

ansible <hosts> -m <module> -i inventory.txt
ansible target1 -m ping  -i inventory.txt
  • ansible-playbookコマンド
    • 宣言的記法。こちらが、ansbile的には王道の書き方。
    • kubectl create -f hoge.yamlと同じアイデア
# ansible-playbookコマンドの例
ansible-playbook <playbook name>
ansible-playbook playbook-webserver.yaml

modules

modulesいろいろ

- 
  name: Play 1
  hosts: localhost
  tasks: 
  - name: Execute command 'date'
    command: date
    
  - name: Display reslov.conf content
    command: cat /etc/resolv.conf
    
  - name: Display reslov.conf content
    command: cat resolv.conf chdir=/etc
    
  - name: Make directory # createとあるが、ifNotExistの意味らしい
    command: mkdir /folder create=/folder
  • command moduleはfree formなので、適当にcat /etc/hostsとか書いても全体をstringと解釈してくれるが、他moduleでは普通ではないことに注意。
  • script

    • ansibleのローカルマシン上のスクリプトを実行する

      • いちいちローカル→リモートにコピーしなくていいので便利
- 
  name: Play 1
  hosts: localhost
  tasks: 
  - name: Run a script on remote server
    script: /some/local/script.sh -arg1 -arg2
  • service

    • サービスのstart, stop, restartなど

    • どちらの記法でもOK

- 
  name: Play 1
  hosts: localhost
  tasks: 
  - name: start the database service
    service: name=postgresql status=started
- 
  name: Play 1
  hosts: localhost
  tasks: 
  - name: start the database service
    service: 
      name: postgresql
      status: started
  • lineinfile
    • 行がなければ追加する(ansible的には「行があることを確認する」)

      • sed的なイメージ
    • /etc/hosts ・ DNSの追加とかに便利

- 
  name: Play 1
  hosts: localhost
  tasks: 
  - name: add nameserver
    lineinfile: 
      path: /etc/resolv.conf
      line: 'nameserver 8.8.8.8'

variable

  • inventoryにもplaybookにも別ファイルにも定義できる。
    • inventoryのansible_host, ansible_connectionも実はvariable。
  • playbookに変数を記載する例
- 
  name: Add DNS server to resolve.conf
  hosts: localhost
  vars: # 変数はここで定義
    dns_server=8.8.8.8
  tasks: 
    - lineinfile: 
        path: /etc/resolv.conf
        line: 'nameserver {{ dns_server }}' # 2重カッコで括って変数を展開
  • inventoryに変数を記載する例
# sample inventory file
Web http_port=8081 
#  playbook
- 
  name: ser firewall config
  hosts: web
  tasks:
  - firewalld:
      service: https
      permanent: true
      state: enabled
  - firewalld: 
      port '{{ http_port }}'/tcp
      parmanent: true
      state: disabled
  • 別ファイルに変数を記載する例
# sample variable fiel - web.yml
http_port: 8081
snmp_port: 161-162
  • 2重かっこで変数を展開する方式は、Jinja2 templating と呼ばれている。
source: {{ http_port }} # NG
source: '{{ http_port }}' # OK。基本は''で囲む
source: Someting{{ http_port }}something # OK。文中のみ''は不要。

conditions (条件文)

  • 通常、ソフトウェアのインストールを行う際は、debian系はapy, redhat系はyumを利用するが・・・ 両方同じplaybookでインストールをできると助かる。
  • conditionの使用例
- 
  name: install NGINX
  hosts: all
  tasks: 
  - name: install NGINX on debian
    apt: 
      name: nginx
      state: present
    when: ansible_os_family == "Debian" and ansible_distribution_version == "16.04" # ビルトイン変数を利用
  - name: install NGINX on redhat
    yum: 
      name: nginx
      state: present
    when: ansible_os_family == "RedHat" or ansible_os_family == "SUSE"
  • loop とwhenの併用例
- name: Install softwares
  hosts: all
  vars:
    packages:
    - name: nginx
      required: True
    - name: mysql
      required: True
    - name: 
      required: False
  tasks: 
  - name: install '{{item.name}}' on debian # リストのアイテムは文字通りitemで呼び出す。属性の呼び方はjqと同じ
    apt: 
      name: '{{item.name}}'
      state: present
    when: item.required == True # whenと併用も可能。この場合nginxとmysqlだけがインストールされる
    loop: '{{packages}}' #変数で定義したリストを展開可能。イテレータ。
  • registerの利用例 (前に実行したタスクの実行結果を引き継ぐ)
- name: check status of a service and email if its down
  hosts: localhost
  tasks: 
  - command: service httpd status 
    register: result # commandタスクの結果をresult変数(?)に格納。
  - mail: 
    to: aaa@gmail.com
    subject: service alert
    body: httpd service is down
    when: result.stdout.find('down') != -1 # findは「見つからなければ-1を返す」ので、downが見つかるとtrue

Role

  • ansibleの宣言的定義は、各サーバに対して、仕事・役割を振っていると考えることができる

  • その意味で、roleという単位でtasksをまとめておくことで、mysqlとして動作してほしいサーバにはmysql roleを渡すだけ、webAPlサーバとして動作してほしいサーバんはhttpd roleを渡すだけ、とする。

  • 「君(サーバ)にやって欲しいこと」の単位でまとめているのでroleと呼ぶ

  • ansible galaxyに沢山roleが公開されているので見てみると良い

    • ansible-galaxy install geerlingguy.mysqlでインストール可能。ansibleが共通で参照するディレクト/etc/ansible/rolesに配置される。
- 
  name: install and configure mysql
  hosts: db-server
  roles: 
    - geerlingguy.mysql # 普通に使える
  • roleを作りたいときはansible-galaxy init role名ディレクトリを自動生成してくれる

[ec2-user@ip-172-31-3-27 ~]$ ansible-galaxy init mysql
- Role mysql was created successfully
[ec2-user@ip-172-31-3-27 ~]$ tree mysql
mysql
・defaults
 ・main.yml
・files
・handlers
 ・main.yml
・meta
 ・main.yml
・README.md
・tasks
 ・main.yml
・templates
・tests
 ・inventory
 ・test.yml
・vars
 ・main.yml

8 directories, 8 files
  • 上記で生成したロールを、rolesディレクトリに配置しておけば、playbook.yamlから参照できるようになる。
my-playbook
・playbook.yaml
・roles
 ・mysql
  ・...
  • あるいは、/etc/ansible/rolesに配置しておけば共通にみることができる

    • /etc/ansible/ansible.cfgroles_pathから変更も可能
    • ansible-galaxy上のroleをインストールするとここに置かれる。

advanced topics

  • windowsマシンにansibleから指示を出したい場合、windowsマシンでwinRMを有効化しないと使えないことに注意。
  • ホスト名にはHost*とかワイルドカードも利用可能
  • custom modules : 自分でpythonで書ける。

Ex

inventory

# Sample Inventory File

# ansible内で使用する別名を定義できる。ansible版のhosts。hostsで定義したホスト名を更に上書きで出来るので、注意深く定義しないと後々面倒なことになりそう。
# linuxサーバはansible_connection=ssh, windowsサーバはansible_connection=winrm
# linuxサーバのパスワードはansible_ssh_pass=Password123!, windowsサーバのパスワードはansible_password=Password123!

# db servers
sql_db1 ansible_host=sql01.xyz.com ansible_connection=ssh ansible_user=root ansible_ssh_pass=Lin$Pass
sql_db2 ansible_host=sql02.xyz.com ansible_connection=ssh ansible_user=root ansible_ssh_pass=Lin$Pass

# web servers
web_node1 ansible_host=web01.xyz.com ansible_connection=winrm ansible_user=administrator ansible_password=Win$Pass
web_node2 ansible_host=web02.xyz.com ansible_connection=winrm ansible_user=administrator ansible_password=Win$Pass
web_node3 ansible_host=web03.xyz.com ansible_connection=winrm ansible_user=administrator ansible_password=Win$Pass

# サーバのグループを定義
[db_nodes]
sql_db1
sql_db2

[web_nodes]
web_node1
web_node2
web_node3

# 1つのサーバーが複数のグループに所属してもよい。
[boston_nodes]
sql_db1
web_node1

[dallas_nodes]
sql_db2
web_node2
web_node3

# グループのグループも作成可能。
[us_nodes:children] # ただし:childrenが必要。
boston_nodes
dallas_nodes

playbook

  • localhostsでdatecat /etc/hostsを実行するplaybook
-
    name: 'Execute two commands on localhost'
    hosts: localhost
    tasks:
        -
            name: 'Execute a date command'
            command: date
        -
            name: 'Execute a command to display hosts file'
            command: cat /etc/hosts

module

  • lineinfile, user, script, serviceを使った。
-
    name: 'Execute a script on all web server nodes and start httpd service'
    hosts: web_nodes
    tasks:
        -
            name: 'Update entry into /etc/resolv.conf'
            lineinfile:
                path: /etc/resolv.conf
                line: 'nameserver 10.1.250.10'
        - 
            name: add user
            user:
                name: web_user
                uid: 1040
                group: developers
            
        -
            name: 'Execute a script'
            script: /tmp/install_script.sh
        -
            name: 'Start httpd service'
            service:
                name: httpd
                state: present

variables

-
    name: 'Update nameserver entry into resolv.conf file on localhost'
    hosts: localhost
    vars: 
        car_model: BMW M3
        country_name: USA
        title: Systems Engineer
    tasks:
        -
            name: 'Print my car model'
            command: 'echo "My car''s model is {{car_model}}"'
        -
            name: 'Print my country'
            command: 'echo "I live in the {{country_name}}"'
        -
            name: 'Print my title'
            command: 'echo "I work as a {{title}}"'

conditions

-
    name: 'Execute a script on all web server nodes'
    hosts: all_servers
    tasks:
        -
            service: 'name=mysql state=started'
            when: ansible_host == "server4.company.com" # db1とかで指定したいところ。できそうだが
-
    name: 'Am I an Adult or a Child?'
    hosts: localhost
    vars:
        age: 25
    tasks:
        -
            command: 'echo "I am a Child"'
            when: age < 18 # これは変数展開しないのか・・・
        -
            command: 'echo "I am an Adult"'
            when: age >= 18 #elseがないのが微妙
  • register
-
    name: 'Add name server entry if not already entered'
    hosts: localhost
    tasks:
        -
            shell: 'cat /etc/resolv.conf'
            register: command_output
        -
            shell: 'echo "nameserver 10.0.250.10" >> /etc/resolv.conf'
            when: 'command_output.stdout.find("10.0.250.10") == -1'

loop

-
    name: 'Print list of fruits'
    hosts: localhost
    vars:
        fruits:
            - Apple
            - Banana
            - Grapes
            - Orange
    tasks:
        -
            command: 'echo "{{item}}"'
            with_items: '{{fruits}}' # loopではなくwith_itemsというディレクティブもある。アイテム名を直接使いたいとき用?
          

tips

  • ssh fingerprintを無視する方法

    • 初めてサーバにsshする際、fingerprintを記憶しますか?というプロンプトが出るが、ansibleがサーバにsshしようとしてこれを検知すると、エラーで止まってしまう。
    • これは面倒なので、自動でfingerprintを追加する方法が用意されている。(セキュリティ的には微妙だが)
    • /etc/ansible/ansible.cfghost_key_checkingをFalseにすればOK。
  • atomのremote sync を使うと、

    • ローカルのIDEで開発
    • 任意のタイミングで内容をansible controllerに同期 できる
  • shell モジュールとcommand モジュールはよく似ているが、 shellは専用のシェルが払い出されて、環境変数を使うことができ、コマンドにリダイレクトを含むこともできる。
  • 変数の展開がよくわからん・・・・→jinja2 templateを学ぶべき
  • デフォルトではamazon linux にはansibleを入れることが出来ない。
amazon-linux-extras | grep ansible # ansible2の設定項目があることを確認。
sudo amazon-linux-extras enable ansible2
sudo yum install -y ansible