Ansibleでcommandを使わないためのモジュール
Playbookを書いているとたまに
- 特定のパッケージがインストールされていたらなにかする
- 特定のユーザが存在したらなにかする
みたいな処理をしたいときがあります。
実際にインストールしたりユーザ作成せずに現状の確認だけをしたいケースです。
そういったときに command
, shell
モジュールを使用することで実現できます。
tasks: - name: check if httpd is installed shell: rpm -qa | grep httpd register: httpd_installed ignore_errors: True check_mode: False changed_when: False - name: print debug: msg: "httpd is installed" when: httpd_installed.rc == 0
ただし上記のやり方は色々めんどくさいこともあります。
changed_when
やignore_errors
等のパラメータの設定が必要- ansible実行時やansible-lintでwarningが出力されがち
そこでcommand
モジュールの代わりになりうるモジュールをいくつか紹介します。
検証環境
ansible: v2.9.9
targetOS: CentOS7.7 and Ubuntu 18.04
stat
stat – Retrieve file or file system status — Ansible Documentation
stat
モジュールはファイルの状態を確認できます。
ファイルの有無のみでなく種類(ファイル/ディレクトリ/リンクなど)やowner等の情報も取得できます。
tasks: - name: check if /etc/hosts exists stat: path: /etc/hosts register: etchosts - name: print debug: msg: "/etc/hosts exists" when: etchosts.stat.exists - name: print debug: msg: "/etc/hosts is directory" when: etchosts.stat.isdir # this should be False
package_facts
package_facts – package information as facts — Ansible Documentation
package_facts
はパッケージがインストールされているかの確認ができます。
取得したパッケージ一覧はansible_facts.packages
に格納されます。
tasks: - name: check packages package_facts: manager: auto - name: print debug: msg: "Version of NetworkManager is {{ ansible_facts.packages['NetworkManager'][0]['version'] }}" when: "'NetworkManager' in ansible_facts.packages"
getent
getent – A wrapper to the unix getent utility — Ansible Documentation
getent
モジュールはgetentコマンドのラッパーでpasswdやgroup等のデータを取得できます。
取得した情報はgetent_***
に格納されます(***は指定したdatabase名)
※取得できる情報は対象のOSによって異なります。
tasks: - name: check users getent: database: passwd - name: print debug: msg: "app1 user exists" when: "'app1' in getent_passwd"
service_facts
service_facts – Return service state information as fact data — Ansible Documentation
service_facts
モジュールは各serviceの情報を取得します。
取得した情報はansible_facts.services
に格納されます。
tasks: - name: check services service_facts: - name: print debug: msg: "firewalld is {{ ansible_facts.services['firewalld.service']['status'] }}" when: "'firewalld.service' in ansible_facts.services"
まとめ
たまたまtwitterでpackage_facts
を知って他にも似たようなモジュールが無いか調べたら色々ありました。
モジュールをなるべく使うことでOSの差分を吸収してくれたりコード量も減るのでよりPlaybookらしいPlaybookになる気がします。
AtCoderで茶色になった記事
お約束のやつです。
先日のABC156でやっと茶色になったのでやったことメモ。
やったこと
コンテスト参加
初参加のABC149からは毎回参加しています(計9回)
ABC149が初参加でしたが、ABC149/150は2連unratedだったので若干心折れかけました。
実行環境構築(python)
Atcoderとバージョンを合わせた環境の構築からテストの自動実行まで。
コンテストでの早解きには必須?だと思うし、過去問解くときにもだいぶ効率よく進められます。
過去問
AtCoder Problemsのスクショです。
大体2ヶ月で150ACくらいです。
主にABCのAからC問題までを順番に解いていっています。
Dは難易度低めであれば解いていますが、解けないことのほうが多いです。
学んだこと
計算量の考え方
計算量(オーダー)の考え方を初めて知った。O(n)など。
これまでは業務上コード実行の速さを考える必要がほとんどなかったので、速度改善の考え方がわかったのは嬉しい。
標準入力の扱い
これまでは大体データは引数かファイルでスクリプトに渡していたので標準入力を扱うのはなんだかんだ初めてだった。
その他
その他AtCoder始めるまで使ったことなかったライブラリ/機能など
- math
- itertools
- fraction
- collections
- set
- pow
今後の予定
数学チックな問題や大きな数(10**9みたいな)が出てくるやつを安定して解けるようにしたい。
ただペルソナを買ってしまったので勉強のペースは少し落ちそう...
Python実行環境構築メモ[Vagrant+Ansible+vscode]
※自分用メモ
AtCoder用のPython実行環境を作る + サンプルのテストの自動化する。
ローカルにpyenvで古いpythonインストールが楽なんだけどイマイチちゃんと動かなかった。
要件
- AtCoderのPythonにバージョンを合わせた実行環境を構築
- ローカルのmacosには古いpythonはインストールしない (3.4.3)
- Visual Studio Codeを使えるようにする
- 各問題のサンプルはコマンド一発でテストできるようにする
手順
vagrantのVM作成
Vagrantファイル抜粋
config.vm.define "vag2" do |node| node.vm.box = "generic/ubuntu1804" node.vm.hostname = "Vag2" node.vm.network :private_network, ip: "192.168.33.12" node.vm.provider :virtualbox do |vb| vb.customize ["modifyvm", :id, "--memory", "1024"] vb.customize ["modifyvm", :id, "--cpus", "1"] vb.customize ["modifyvm", :id, "--ioapic", "on"] end node.vm.synced_folder "./syncdir", "/vagrant", create: true, owner: "vagrant", group: "vagrant" node.vbguest.auto_update = false end
メモ
- boxは
generic/ubuntu1804
とbento/centos-7.7
で確認済み vagrant ssh
ではなくターミナルからsshコマンドで接続する- ディレクトリ同期する(local:./syncdir to vm:/vagrant)
pythonインストール
Ansible実行。pyenvでpythonとライブラリをインストール
sample問題取得
下記ツールをローカルで使用する。超便利。
同期しているディレクトリをworkspaceにして実行
atcoder-tools gen abc148 --lang python --workspace ~/vag_test/syncdir/atcoder
テスト用シェルもコピーしておく
cp -p /path/to/atcoder-python-role/test.sh ~/vag_test/syncdir/atcoder/.
vscodeでVagrantのVMに接続
上記extensionを使用
コード書いてテスト
- 各問題のディレクトリのmain.pyにコードを記載 (Ex:
/vagrant/atcoder/abc148/A/main.py
) - テストシェル実行
cd /vagrant/atcoder/ && ./test.sh abc148 A
まとめ
職業柄環境構築や自動化のほうが捗る(過去問解け)
Ansibleのshellモジュールで、falseコマンドを実行できない?
※超小ネタです。
先日Playbookがコケたときの挙動を確認すべく下記のようなtaskを作成しましたが、なぜかsyntaxのエラーが出てしまいました。
% cat test.yml --- - hosts: Vag1 gather_facts: False tasks: - name: fail on purpose shell: false
% ansible-playbook test.yml --syntax-check ERROR! unexpected parameter type in action: <class 'bool'> The error appears to be in '/Users/koh/vag_test/test.yml': line 5, column 7, but may be elsewhere in the file depending on the exact syntax problem. The offending line appears to be: tasks: - name: fail on purpose ^ here %
原因
原因は false
を実行するコマンドとして書いたつもりが、Ansibleにbooleanとして認識されてしまっているようです。
エラー内容を確認するとbooleanを指定すべきではないよ。と出ています。
ERROR! unexpected parameter type in action: <class 'bool'>
shellモジュールは値としてstringを指定してあげる必要があります。
= free_form The shell module takes a free form command to run, as a string. There is no actual parameter named 'free form'. See the examples on how to use this module. type: str
※ansible-docの出力抜粋
対策
今回の場合は false
をクォーテーションでくくってあげるとstrとして認識されるので問題なくplaybookを実行できます。
[koh@kohs-MBP] ~/vag_test % cat test.yml --- - hosts: Vag1 gather_facts: False tasks: - name: fail on purpose shell: "false" [koh@kohs-MBP] ~/vag_test % ansible-playbook test.yml --syntax-check playbook: test.yml [koh@kohs-MBP] ~/vag_test % ansible-playbook test.yml PLAY [Vag1] ***************************************************************************************** TASK [fail on purpose] ****************************************************************************** fatal: [Vag1]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": true, "cmd": "false", "delta": "0:00:00.005244", "end": "2020-01-16 13:22:40.832329", "msg": "non-zero return code", "rc": 1, "start": "2020-01-16 13:22:40.827085", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []} PLAY RECAP ****************************************************************************************** Vag1 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 [koh@kohs-MBP] ~/vag_test %
playbookの実行を失敗させるのであればfailモジュールを使用するのもありです。
まとめ
Ansibleがうまく動かないときはだいたい型が間違ってる気がします。(自分の場合)
2019年を雑に振り返る
2019年の出来事を振り返る。
ブログやtwitterを見ながら思い出せる範囲で。
エンジニア関連
ブログ開設
当ブログを開設してちまちま更新している。
また最初は英語でも同じブログに投稿していたが今はdev.toに切り替えた。
内容を振り返ると大体Ansible。
GitHubへのアウトプットを徐々に始める
これまでは職場のPrivateリポジトリのみで使用していたが少しずつ自作のスクリプトやPlaybook等を公開するようにした。
それ以外にも職務経歴書(的なやつ)や読書記録等にも使い始めた。
AtCoder参加(一回だけ)
今年最後のABCになんとなくで参加してみたがunratedとなり灰色になれなかった。
来年からちまちま参加し緑くらいを目指したい。
生活
禁煙
長年タバコを吸い続けていたがずっと喉の調子が良くなく試しに禁煙。
それ以来禁煙は続いていて現在300日以上続いている。
喉の調子はそこまで変わらないが体重はみるみる増えた。
自宅のインターネットをwimaxから固定回線に切替
長らくwimaxを使っていたが通信制限に耐えられずついに切り替えた。
しかし自宅がVDSLでかなり遅い。つらい。
今の自宅の唯一の不満点となる。
主な買ったもの
iPad mini
とても良い。非常に使い勝手がよく通勤中のアマプラ/読書に役立っている。
今年購入したものの中でトップレベルに満足している。
Raspberry Pi3 Model B+
なんとなく物理レイヤーの理解が深まった気がする。
mackerelで監視までしているけれど特に意味なく稼働している。
iPhone11
顔認証を嫌って7のまま我慢していたが意外と快適だった。画面でかくて良い。
switch
リングフィット購入し10年ぶりくらいに楽しく運動できている。
まとめ
エンジニアとしてのアウトプットが増えたことと禁煙が大きいかなーと思う。
そろそろ英語がヤバそうなので来年から本格的にブラッシュアップしたい。
telnetで疎通確認みたいなことをAnsibleでやる
DNSやrouting、firewallまわりの設定が正しくできているかの確認として、telnetやncコマンドで疎通確認するみたいな作業はインフラエンジニアにはよくある光景だと思います。
しかしどちらのコマンドも必ずサーバにインストールされているとは限らないですし、何よりAnsibleで使うには相性が悪そうです。
そんなときにはwait_for
モジュールが便利です。
wait_forモジュールとは
このモジュールの主なユースケースとしては
- Javaなどのアプリをstartしてから実際にポートがLISTEN状態になるまで待つ
- ログを確認して特定の文字列が書かれるまで待つ
など、非同期なタスクをシーケンシャルに実施したいときに役立ちます。
しかしタイムアウトを短くして使用すればポートレベルの疎通確認ができます。
使用例
※Ansible 2.9.0を使用しています
例として下記のようなPlaybookを作成しました。
% cat test.yml --- - hosts: Vag1 gather_facts: False tasks: - name: check if github.com:22 is accessible wait_for: host: github.com port: 22 state: started delay: 0 timeout: 1 - name: check if 192.168.33.12:25 is accessible wait_for: host: 192.168.33.12 port: 25 state: started delay: 0 timeout: 1 search_regex: Postfix
実際に実行してみます。
[koh@kohs-MBP] ~/vag_test % ansible-playbook test.yml PLAY [Vag1] ***************************************************************************************** TASK [check if github.com:22 is accessible] ********************************************************* ok: [Vag1] TASK [check if 192.168.33.12:25 is accessible] ****************************************************** ok: [Vag1] PLAY RECAP ****************************************************************************************** Vag1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [koh@kohs-MBP] ~/vag_test %
疎通に問題がなければ結果はいずれもok
になります。
次に192.168.33.12(仮メールサーバ)にてfirewalldを起動してアクセスをブロックします。
[vagrant@Vag2] ~ % hostname -I 10.0.2.15 192.168.33.12 [vagrant@Vag2] ~ % sudo systemctl start firewalld [vagrant@Vag2] ~ %
再度実行してみます。
[koh@kohs-MBP] ~/vag_test % ansible-playbook test.yml PLAY [Vag1] ***************************************************************************************** TASK [check if github.com:22 is accessible] ********************************************************* ok: [Vag1] TASK [check if 192.168.33.12:25 is accessible] ****************************************************** fatal: [Vag1]: FAILED! => {"changed": false, "elapsed": 1, "msg": "Timeout when waiting for search string Postfix in 192.168.33.12:25"} PLAY RECAP ****************************************************************************************** Vag1 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 zsh: exit 2 ansible-playbook test.yml [koh@kohs-MBP] ~/vag_test %
上記のようにエラーとなり疎通できていないことがわかります。
まとめ
Ansible 疎通確認
などで検索してもAnsible実行ノードとターゲットノードとの疎通の話しか出てこなかったのでちょっと手こずりました。
参考
ansible-lintの設定
ansible-lintというAnsibleのPlaybookの問題点を指摘してくれるツールがあります。
無駄なスペースを検知してくれたり、Playbookのベストプラクティスに沿わないような記載を指摘してくれます。
[koh@kohs-MBP] ~/work/linttest % ansible-lint site.yml [201] Trailing whitespace site.yml:6 msg: hello [403] Package installs should not use latest site.yml:8 Task/Handler: install httpd [koh@kohs-MBP] ~/work/linttest %
デフォルトルールの一覧はこちらです。
Playbookの書き方や運用の仕方によっては特定のルールを無視したり別途ルールを追加したりしたい場合もあると思います。
そのための設定方法についてまとめました。
※ ansible-lintのバージョンは 4.1.0
をもとに説明しています。
コマンドラインでの設定
コマンドラインのオプションでも各ルールを設定できます。
オプション一覧(クリックで展開)
[koh@kohs-MBP] ~/work/linttest
% ansible-lint --help
Usage: ansible-lint [options] playbook.yml [playbook2 ...]
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-L list all the rules
-q quieter, although not silent output
-p parseable output in the format of pep8
--parseable-severity parseable output including severity of rule
-r RULESDIR specify one or more rules directories using one or
more -r arguments. Any -r flags override the default
rules in
/Users/koh/.pyenv/versions/3.7.3/lib/python3.7/site-
packages/ansiblelint/rules, unless -R is also used.
-R Use default rules in
/Users/koh/.pyenv/versions/3.7.3/lib/python3.7/site-
packages/ansiblelint/rules in addition to any extra
rules directories specified with -r. There is no need
to specify this if no -r flags are used
-t TAGS only check rules whose id/tags match these values
-T list all the tags
-v Increase verbosity level
-x SKIP_LIST only check rules whose id/tags do not match these
values
--nocolor disable colored output
--force-color Try force colored output (relying on ansible's code)
--exclude=EXCLUDE_PATHS
path to directories or files to skip. This option is
repeatable.
-c C Specify configuration file to use. Defaults to
".ansible-lint"
[koh@kohs-MBP] ~/work/linttest
%
基本的にはコマンドラインでのオプションですべて設定できるのですが、チームでルールを共有したりCIに組み込んだりする際には設定ファイルに指定するケースが多いと思います。
設定ファイルでの記載方法について書いていきます。
設定ファイル
デフォルトでは ./.ansible-lint
、もしくはオプション -c
で指定したファイルになります。
公式の例はこのような形になっています。
exclude_paths: - ./my/excluded/directory/ - ./my/other/excluded/directory/ - ./last/excluded/directory/ parseable: true quiet: true rulesdir: - ./rule/directory/ skip_list: - skip_this_tag - and_this_one_too - skip_this_id - '401' tags: - run_this_tag use_default_rules: true verbosity: 1
https://docs.ansible.com/ansible-lint/configuring/configuring.html#configuration-file
それぞれの設定項目について説明していきます。
exclude_paths (--exclude)
チェックから除外するパス/ファイルを指定します。
parseable (-p)
出力の形式を変更できます。
複数行に分かれているものが1行にまとまります。
# parseable: false [koh@kohs-MBP] ~/work/linttest % ansible-lint site.yml [201] Trailing whitespace /Users/koh/work/linttest/roles/201/tasks/main.yml:4 msg: hello [202] Octal file permissions must contain leading zero or be a string /Users/koh/work/linttest/roles/202/tasks/main.yml:2 Task/Handler: error 202 [203] Most files should not contain tabs /Users/koh/work/linttest/roles/203/tasks/main.yml:4 msg: " tab" [koh@kohs-MBP] ~/work/linttest %
# parseable: true [koh@kohs-MBP] ~/work/linttest % ansible-lint site.yml /Users/koh/work/linttest/roles/201/tasks/main.yml:4: [E201] Trailing whitespace /Users/koh/work/linttest/roles/202/tasks/main.yml:2: [E202] Octal file permissions must contain leading zero or be a string /Users/koh/work/linttest/roles/203/tasks/main.yml:4: [E203] Most files should not contain tabs [koh@kohs-MBP] ~/work/linttest %
quiet (-q)
出力形式を変更できます。
0にはなりませんが少し減ります。
# quiet: true [koh@kohs-MBP] ~/work/linttest % ansible-lint site.yml [201] /Users/koh/work/linttest/roles/201/tasks/main.yml:4 [202] /Users/koh/work/linttest/roles/202/tasks/main.yml:2 [203] /Users/koh/work/linttest/roles/203/tasks/main.yml:4 [koh@kohs-MBP] ~/work/linttest %
rulesdir (-r)
自作のruleを格納したファイル/ディレクトリを指定できます。
自作ruleについては後述します。
skip_list (-x)
スキップするタグやエラーIDを指定できます。
タグについては -T
オプションで一覧を出力できます。
タグ一覧(クリックで展開)
[koh@kohs-MBP] ~/work/linttest
% ansible-lint -T
ANSIBLE0002 ['[201]']
ANSIBLE0004 ['[401]']
ANSIBLE0005 ['[402]']
ANSIBLE0006 ['[303]']
ANSIBLE0007 ['[302]']
ANSIBLE0008 ['[103]']
ANSIBLE0009 ['[202]']
ANSIBLE0010 ['[403]']
ANSIBLE0011 ['[502]']
ANSIBLE0012 ['[301]']
ANSIBLE0013 ['[305]']
ANSIBLE0014 ['[304]']
ANSIBLE0015 ['[104]']
ANSIBLE0016 ['[503]']
ANSIBLE0017 ['[501]']
ANSIBLE0018 ['[101]']
ANSIBLE0019 ['[102]']
behaviour ['[503]']
bug ['[304]']
command-shell ['[305]', '[302]', '[304]', '[306]', '[301]', '[303]']
deprecated ['[105]', '[104]', '[103]', '[101]', '[102]']
formatting ['[104]', '[203]', '[201]', '[204]', '[206]', '[205]', '[202]']
idempotency ['[301]']
idiom ['[601]', '[602]']
metadata ['[701]', '[704]', '[703]', '[702]']
module ['[404]', '[401]', '[403]', '[402]']
oddity ['[501]']
readability ['[502]']
repeatability ['[401]', '[403]', '[402]']
resources ['[302]', '[303]']
safety ['[305]']
task ['[502]', '[503]', '[504]', '[501]']
[koh@kohs-MBP] ~/work/linttest
%
tags (-t)
skip_listの逆でこの値が指定された場合は指定されたタグのみチェックします。
ちなみにskip_listとtags両方に同じ値を設定するとスキップされるみたいです。
use_default_rules (-R)
trueの場合デフォルトのルールが適用されます。
自作のルールのみチェックしたい場合にはfalseに指定します。
verbosity (-v)
出力の詳細度を指定できます。
数値で出力量が変わりそうに見えますが現状0か1以上かでのみ挙動が変わりそうです。
for file in files: if self.verbosity > 0: print("Examining %s of type %s" % (file['path'], file['type'])) matches.extend(self.rules.run(file, tags=set(self.tags), skip_list=self.skip_list))
Playbook内での設定
Playbook内でコメントを入れることにより行単位でチェック対象から外すことができます。
例として下記のようなPlaybookを作成します。
[koh@kohs-MBP] ~/work/linttest % cat site.yml --- - hosts: all tasks: - name: install latest httpd yum: name: httpd state: latest - name: install mysql yum: name: mysql state: installed [koh@kohs-MBP] ~/work/linttest %
このPlaybookに対してansible-lintを実行すると403のエラーが返されます。
[koh@kohs-MBP] ~/work/linttest % ansible-lint site.yml [403] Package installs should not use latest site.yml:4 Task/Handler: install latest httpd [koh@kohs-MBP] ~/work/linttest %
どうしてもhttpdは常に最新にしておきたいけど他のパッケージはlatestにはしたくないといったとき(多分無い)には下記のように記載できます。
[koh@kohs-MBP] ~/work/linttest % cat site.yml --- - hosts: all tasks: - name: install latest httpd yum: name: httpd state: latest # noqa 403 - name: install mysql yum: name: mysql state: installed [koh@kohs-MBP] ~/work/linttest %
実際にエラーを返す行に # noqa ID
とコメントを入れるとチェック対象から除外されます。
[koh@kohs-MBP] ~/work/linttest % ansible-lint site.yml [koh@kohs-MBP] ~/work/linttest %
自作ルール
ansible-lint用のルールを自作することができます。
READMEにも作成の仕方が記載されていますが、デフォルトルールのスクリプトを参考にするとわかりやすいと思います。
https://github.com/ansible/ansible-lint#creating-custom-rules
https://github.com/ansible/ansible-lint/tree/master/lib/ansiblelint/rules
試しに一つ自作ルールを作成してみます。
適当なディレクトリを作成してルール用のスクリプトを作成します。
[koh@kohs-MBP] ~/work/linttest % cat origrules/True4BooleanRule.py from ansiblelint import AnsibleLintRule class True4BooleanRule(AnsibleLintRule): id = '99' shortdesc = 'Use "True" for boolean' description = 'We should "True" for boolean not "yes" or "true".' tags = ['formatting'] def match(self, file, line): return ': yes' in line or ': true' in line [koh@kohs-MBP] ~/work/linttest %
PlaybookではbooleanのTrueを指定する際に True
以外にも yes
, true
なども使用できます。
このルールでは先頭大文字の True
に統一するべく yes
, true
を検知してエラーを出します。
※例のためのスクリプトなのでかなり雑に書いています。使用する際には適宜修正してください。
.ansible-lint
に下記のように記載します。
[koh@kohs-MBP] ~/work/linttest % cat .ansible-lint rulesdir: - ./origrules/ [koh@kohs-MBP] ~/work/linttest %
site.ymlを下記のようにすると3つのタスクのうち上2つが目論見通りエラーになります。
[koh@kohs-MBP] ~/work/linttest % cat site.yml --- - hosts: all tasks: - name: this should be error service: name: httpd state: started enabled: yes - name: this should be error too service: name: mysqld state: started enabled: true - name: this is ok service: name: haproxy state: started enabled: True [koh@kohs-MBP] ~/work/linttest % ansible-lint site.yml [99] Use "True" for boolean site.yml:8 enabled: yes [99] Use "True" for boolean site.yml:14 enabled: true [koh@kohs-MBP] ~/work/linttest %
まとめ
設定ファイルでの各パラメータの説明等が公式ドキュメントに見当たらなかったのでまとめてみました。
自作ルールの作成が思ったよりも簡単だったので色々試してみたいですね。