はじめに
Ansible Playbookでユーザーを複数作成する場合、あらかじめリスト変数にユーザー名、UID、グループ名等々を設定しておき、userモジュール+ループで一括作成、というのが通常かと思います。
ただ、あまりに大量のユーザーを作成する場合、そのリスト変数を用意するのが面倒です。
連番のユーザーをサクッと作成するPlaybookがあればいいのに、と思っていたところ、ようやくそれなりのものが出来たので、記念に残します。
想定ケース
例えば開発者向けなど、連番のユーザーを大量に作成する。
環境
VirtualBox (+ vagrant)
CentOS 7.7
ansible 2.9.6
Playbook
今回も記事を分かりやすくするため、1本のPlaybookusercreate.yml
に全て記載します。(ベストプラクティスを知らないわけではないです。念の為。)
まず冒頭で、各種変数を準備します。この例では、01から20までの連番のユーザーを作成します。詳しい説明はコメントを参照ください。
---
- name: Create many user
hosts: localhost
vars:
# 1から20までをカンマで繋いだ文字列を作ります。最後はカンマで終わりますが、後でなんとかします。
user_num_data: "{% for n in range(20) %}{{n+1}},{% endfor %}"
# 上で作った変数をカンマで分割して、リスト変数に登録します。最後の要素は空ですが、後でなんとかします。
user_num: "{{ user_num_data.split(',') }}"
# 作成するユーザーの初期パスワードです
initialpw: zaq12wsx
# UID/GIDのプレフィックスです。5000番台を使います。
pre_id: 50
# ユーザー名のプレフィックスです。
pre_name: devuser
# 作成するユーザー全員を登録する共通グループです。
common_group: devgroup
続いてタスク定義です。まずは共通グループを作成します。ユーザーで使用しない0番、GID=5000で作成します。
tasks:
- name: Common group is created
group:
name: '{{ common_group }}'
gid: '{{ pre_id }}00'
state: present
次は作成するユーザーごとのグループを作成します。ループするリスト変数を'{{ user_num[:-1] }}'
とすることで、最後の空要素を除外します。そして、ユーザー名などに連番を使用する際、2桁に揃えるために、item.zfill(2)
とします。
- name: Groups are created
group:
name: '{{ pre_name }}{{ item.zfill(2) }}'
gid: '{{ pre_id }}{{ item.zfill(2) }}'
state: present
with_items:
- '{{ user_num[:-1] }}'
最後にユーザーを作成します。
- name: Users are created
user:
name: '{{ pre_name }}{{ item.zfill(2) }}'
group: '{{ pre_name }}{{ item.zfill(2) }}'
groups: '{{ pre_name }}{{ item.zfill(2) }}, {{ common_group }}'
uid: '{{ pre_id }}{{ item.zfill(2) }}'
state: present
password: "{{ initialpw | password_hash('sha512') }}"
update_password: on_create
with_items:
- '{{ user_num[:-1] }}'
register: usercreated
おまけで、初期パスワードの変更を促すために、パスワードを無効にします。前のタスクで、ユーザーが作成された時にのみ実行されるようにwhenで条件を指定します。あと、ansible-playbook実行時の出力を抑えるために、loop_controlのlabelを指定します。
- name: Passwords are expired
shell: |
passwd -e '{{ item.invocation.module_args.name }}'
with_items:
- '{{ usercreated.results }}'
when:
- item.changed
- item.invocation.module_args.state == "present"
loop_control:
label: "{{ item.invocation.module_args.name }}"
実行例
普通の実行結果です。
$ ansible-playbook -i inventories/test usercreate.yml
PLAY [Create many user] ************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************
ok: [localhost]
TASK [Common group is created] *****************************************************************************************
changed: [localhost]
TASK [Groups are created] **********************************************************************************************
changed: [localhost] => (item=1)
changed: [localhost] => (item=2)
changed: [localhost] => (item=3)
changed: [localhost] => (item=4)
changed: [localhost] => (item=5)
changed: [localhost] => (item=6)
changed: [localhost] => (item=7)
changed: [localhost] => (item=8)
changed: [localhost] => (item=9)
changed: [localhost] => (item=10)
changed: [localhost] => (item=11)
changed: [localhost] => (item=12)
changed: [localhost] => (item=13)
changed: [localhost] => (item=14)
changed: [localhost] => (item=15)
changed: [localhost] => (item=16)
changed: [localhost] => (item=17)
changed: [localhost] => (item=18)
changed: [localhost] => (item=19)
changed: [localhost] => (item=20)
TASK [Users are created] ***********************************************************************************************
changed: [localhost] => (item=1)
changed: [localhost] => (item=2)
changed: [localhost] => (item=3)
changed: [localhost] => (item=4)
changed: [localhost] => (item=5)
changed: [localhost] => (item=6)
changed: [localhost] => (item=7)
changed: [localhost] => (item=8)
changed: [localhost] => (item=9)
changed: [localhost] => (item=10)
changed: [localhost] => (item=11)
changed: [localhost] => (item=12)
changed: [localhost] => (item=13)
changed: [localhost] => (item=14)
changed: [localhost] => (item=15)
changed: [localhost] => (item=16)
changed: [localhost] => (item=17)
changed: [localhost] => (item=18)
changed: [localhost] => (item=19)
changed: [localhost] => (item=20)
TASK [Passwords are expired] *******************************************************************************************
changed: [localhost] => (item=devuser01)
changed: [localhost] => (item=devuser02)
changed: [localhost] => (item=devuser03)
changed: [localhost] => (item=devuser04)
changed: [localhost] => (item=devuser05)
changed: [localhost] => (item=devuser06)
changed: [localhost] => (item=devuser07)
changed: [localhost] => (item=devuser08)
changed: [localhost] => (item=devuser09)
changed: [localhost] => (item=devuser10)
changed: [localhost] => (item=devuser11)
changed: [localhost] => (item=devuser12)
changed: [localhost] => (item=devuser13)
changed: [localhost] => (item=devuser14)
changed: [localhost] => (item=devuser15)
changed: [localhost] => (item=devuser16)
changed: [localhost] => (item=devuser17)
changed: [localhost] => (item=devuser18)
changed: [localhost] => (item=devuser19)
changed: [localhost] => (item=devuser20)
PLAY RECAP *************************************************************************************************************
localhost : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
/etc/passwdを見ると、綺麗に連番で作成されています。
$ grep devuser /etc/passwd
devuser01:x:5001:5001::/home/devuser01:/bin/bash
devuser02:x:5002:5002::/home/devuser02:/bin/bash
devuser03:x:5003:5003::/home/devuser03:/bin/bash
devuser04:x:5004:5004::/home/devuser04:/bin/bash
devuser05:x:5005:5005::/home/devuser05:/bin/bash
devuser06:x:5006:5006::/home/devuser06:/bin/bash
devuser07:x:5007:5007::/home/devuser07:/bin/bash
devuser08:x:5008:5008::/home/devuser08:/bin/bash
devuser09:x:5009:5009::/home/devuser09:/bin/bash
devuser10:x:5010:5010::/home/devuser10:/bin/bash
devuser11:x:5011:5011::/home/devuser11:/bin/bash
devuser12:x:5012:5012::/home/devuser12:/bin/bash
devuser13:x:5013:5013::/home/devuser13:/bin/bash
devuser14:x:5014:5014::/home/devuser14:/bin/bash
devuser15:x:5015:5015::/home/devuser15:/bin/bash
devuser16:x:5016:5016::/home/devuser16:/bin/bash
devuser17:x:5017:5017::/home/devuser17:/bin/bash
devuser18:x:5018:5018::/home/devuser18:/bin/bash
devuser19:x:5019:5019::/home/devuser19:/bin/bash
devuser20:x:5020:5020::/home/devuser20:/bin/bash
/etc/groupも同様。
$ grep dev /etc/group
devgroup:x:5000:devuser01,devuser02,devuser03,devuser04,devuser05,devuser06,devuser07,devuser08,devuser09,devuser10,devuser11,devuser12,devuser13,devuser14,devuser15,devuser16,devuser17,devuser18,devuser19,devuser20
devuser01:x:5001:devuser01
devuser02:x:5002:devuser02
devuser03:x:5003:devuser03
devuser04:x:5004:devuser04
devuser05:x:5005:devuser05
devuser06:x:5006:devuser06
devuser07:x:5007:devuser07
devuser08:x:5008:devuser08
devuser09:x:5009:devuser09
devuser10:x:5010:devuser10
devuser11:x:5011:devuser11
devuser12:x:5012:devuser12
devuser13:x:5013:devuser13
devuser14:x:5014:devuser14
devuser15:x:5015:devuser15
devuser16:x:5016:devuser16
devuser17:x:5017:devuser17
devuser18:x:5018:devuser18
devuser19:x:5019:devuser19
devuser20:x:5020:devuser20
パスワードもexpireされてます。
$ sudo chage -l devuser01
Last password change : password must be changed
Password expires : password must be changed
Password inactive : password must be changed
Account expires : never
Minimum number of days between password change : 0
Maximum number of days between password change : 99999
Number of days of warning before password expires : 7
ユーザーが足らなくなったら、以下のrange(20)
のところをrange(40)
などとして、Playbookを再実行すれば、作成済みのユーザーには影響なく、追加分のみ作成してくれます。
user_num_data: "{% for n in range(20) %}{{n+1}},{% endfor %}"
これで、数十人のメンバーがいるチームが複数立ち上がっても、ドンとこい!です。