概要
Ansibleを使用することで、さまざまな作業が自動化できる。
しかし、IPがついていない機器に対してリモート作業を行うのは難しい。
ここでは、KVMのホスト上からAnsibleでIPを付与するまでを自動化させ、リモートでSSHできるまでの手法を記載する。
なお、ユーザーと公開鍵は事前にvirtcloneするマシンの中に含まれているものとする。
構成
- ansibleサーバ1台
- ansible実行先仮想ホスト
- 仮想ホストで構築されるVM
検証環境は、Almalinux9で構築する。
実行手順
以下のようなAnsible構成である。
- roles
- kvm_create_vm
- vars
- main.yml
- task
- main.yml
- vars
- kvm_configure_vm
- vars
- main.yml
- task
- main.yml
- vars
- kvm_create_vm
- inventory.yml
- create-yml
- secrets.yml
各ファイルの構成は以下の通り。
inventory.yml
仮想ゲストのIPと、仮想ホストのIPをいれる。ゲストのオプションは、初めてのホストにsshするためにつける。
[kvm_host]
仮想ホストのIP
[vm_ip]
仮想ゲストのIP ansible_ssh_common_args='-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'
secrets.yml
ansible vaultで暗号化したユーザー名とパスワードを入れてある。
vault_user: "linuxのユーザ名"
vault_pass: "ユーザーのパスワード"
create-yml
各々のタスクを実行する。それぞれ、inventory.ymlと、secrets.ymlを指定している。
---
- name: play create vm
hosts: kvm_host
vars_files:
- secrets.yml
roles:
- { role: kvm_create_vm, become: yes }
- name: play configure vm
hosts: vm_ip
vars_files:
- secrets.yml
roles:
- {role: kvm_configure_vm, become: yes}
kvm-create/task/main.yml
interfaceの指定の部分は、ここではシェルで指定しているが、libvirtのNIC付与コマンドを後程指定してやること。
実は、ここの中で、ボリュームの作成等で対話型の内容が出てくるので、expectモジュール
を使用して、想定する返答をここに記載している。自分のいるフォルダを指定して、コマンドが終わったら別のフォルダに移動することでコマンドを実行させないようにしている。
---
- name: Clone_a_KVM-irtual-machine
command: virt-clone --original template-vmguest --name {{ vmname }} --auto-clone
become: yes
- name: Create_Network_Interfaces
command: /opt/kickstart/setup/kvm/attach-nic.sh {{ vmname }} {{ item.bridge }} {{ item.ip }}
loop: "{{ bridge_ip_mapping }}"
become: yes
- name: CPUmax
command: virsh setvcpus {{ vmname }} {{ cpus }} --config --maximum
- name: Memorymax
command: virsh setmaxmem {{ vmname }} {{ memorys }} --config
- name: CPUcurrent
command: virsh setvcpus {{ vmname }} {{ cpus }} --config
- name: Memorycurrent
command: virsh setmem {{ vmname }} {{ memorys }} --config
- name: Volume_add
command: virsh vol-resize /var/lib/libvirt/images/{{ vmname }}.img {{ VolumeSize }}GiB
when: VolumeSize != 'default'
- name: Run an interactive command using expect
expect:
command: sudo parted /var/lib/libvirt/images/{{ vmname }}.img print
responses:
"Fix": fix
when: VolumeSize != 'default'
become: yes
- name: test-resize
command: sudo parted /var/lib/libvirt/images/{{ vmname }}.img resizepart 3 100%
when: VolumeSize != 'default'
- name: kpartx
command: sudo kpartx -av /var/lib/libvirt/images/{{ vmname }}.img
when: VolumeSize != 'default'
- name: mount
command: sudo mount /dev/mapper/loop0p3 /mnt
when: VolumeSize != 'default'
- name: xfs_growfs
command: sudo xfs_growfs /mnt
when: VolumeSize != 'default'
- name: umount
command: sudo umount /mnt
when: VolumeSize != 'default'
- name: kpartxd
command: sudo kpartx -d /var/lib/libvirt/images/{{ vmname }}.img
when: VolumeSize != 'default'
# to use expect task
- name: utf8-define
command: sudo localectl set-locale LANG=ja_JP.UTF-8
- name: start vm
virt:
name: "{{ vmname }}"
state: running
- name: Wait for 40 seconds to ensure the VM is up
pause:
seconds: 40
- name: Connect to VM and enter username after escape character prompt
expect:
command: sudo virsh console {{ vmname }}
responses:
'Connected to domain': '{{vault_user}}'
'パスワード': '{{vault_pass}}'
'Password:': '{{vault_pass}}'
'~': 'sudo systemd-machine-id-setup && cd /tmp'
'UUID': 'sudo reboot'
'/tmp': 'sudo reboot'
timeout: 50
ignore_errors: yes
- name: Wait for 40 seconds to ensure the VM is up
pause:
seconds: 40
- name: make_firewall
expect:
command: sudo virsh console {{ vmname }}
responses:
'Connected to domain': '{{vault_user}}'
'パスワード': '{{vault_pass}}'
'Password:': '{{vault_pass}}'
'~': 'sudo firewall-cmd --zone=trusted --add-source=xx.xx.x.x/8 && sudo firewall-cmd --runtime-to-permanent && cd /tmp'
timeout: 10
ignore_errors: yes
- name: Connect to VM and make mgmt-ip
expect:
command: sudo virsh console {{ vmname }}
responses:
'Connected to domain': '{{vault_user}}'
'パスワード': '{{vault_pass}}'
'Password:': '{{vault_pass}}'
'tmp': 'sudo nmcli con add type 802-3-ethernet ifname {{management_interfaces}} con-name {{management_interfaces}} autoconnect no ipv4.method manual ipv4.address "{{management_ip}}" ipv4.gateway {{management_gw}} ipv4.dns c.c.c.c ipv4.dns-options timeout:1,attempts:1,single-request-reopen ipv6.method ignore && cd /var'
'var': 'sudo nmcli c up enp1s0f1 && cd /home'
timeout: 10
ignore_errors: yes
kvm-create/vars/main.yml
IPアドレスを指定すると、ブリッジのMACアドレスを書き換えてくれる。
また、インターフェースがethになるか、enp1s0f0になるか等は事前に調べておく必要がある。
vmname: test-vmname-1
bridge_ip_mapping:
- bridge: bridgename
ip: 2.2.2.2
- bridge: bridgename
ip: 3.3.3.3
cpus: 2
memorys: 8GiB
# if VolumuSize not change "VolumeSize: default"
# if you change volume size 60G, -> VolumeSize: 60
VolumeSize: default
# I couldn't for the life of me determine the interface from the mac address.
management_interfaces: enp1s0f1
management_ip: 2.2.2.2/x
management_gw: 2.2.2.254
kvm_configure_vm/tasks/main.yml
ここで、KVMで新しく作成した仮想マシンをhostにしてタスクが実行されていく。
最初のcreateのタスクで、ゲートウェイをあらかじめ設定してしまっているので、ルーティングの情報を書き換えるようなコマンドを実行している。
また、インターフェースに関連したタスクは、後述のvars/main.ymlに記載したインターフェースの数だけループさせている。
---
- name: Set hostname
ansible.builtin.hostname:
name: "{{hostname}}"
- name: Add hostname entry to /etc/hosts
ansible.builtin.lineinfile:
path: /etc/hosts
line: "127.0.1.1 {{ ansible_fqdn }} {{ ansible_hostname }}"
state: present
create: yes
- name: Update hostname in Zabbix agent configuration
ansible.builtin.replace:
path: /etc/zabbix/zabbix_agent2.conf
regexp: '^Hostname=.*'
replace: 'Hostname={{ ansible_hostname }}'
- name: Add Ethernet connection
nmcli:
conn_name: "{{item.name}}"
ifname: "{{item.name}}"
type: ethernet
autoconnect: true
state: present
ip4: "{{ item.ip | default(omit) }}"
gw4: "{{ item.gateway | default(omit) }}"
dns4: "{{item.dns | default(omit)}}"
loop: "{{interfaces}}"
- name: fix_maneged_routing
ansible.builtin.command:
cmd: nmcli connection modify "{{management_interface}}" +ipv4.routes "x.x.x.x/y {{management_gw}}"
- name: restart_interfaces
ansible.builtin.command:
cmd: sudo nmcli connection up "{{item.name}}"
loop: "{{interfaces}}"
- name: restart_mgmt_interface
ansible.builtin.command:
cmd: sudo nmcli connection up "{{management_interface}}"
# if you already installed,skip this task
- name: install_shell from git
ansible.builtin.command:
cmd: sudo git clone https://gitlab.stream.co.jp/infra/alma9-setup.git /opt/kickstart/setup
ignore_errors: yes
# Loop over as many interfaces as there are interfaces.
- name: make_firewalld_zone
ansible.builtin.command:
cmd: sudo /opt/kickstart/setup/basic/firewalld-zone.sh "{{item.firewallzone}}" "{{item.name}}"
loop: "{{interfaces}}"
- name: modify_firewalld
ansible.builtin.command:
cmd: sudo nmcli con mod "{{item.name}}" connection.zone "{{item.firewallzone}}"-"{{item.name}}"
loop: "{{interfaces}}"
- name: restart_interface
ansible.builtin.command:
cmd: sudo nmcli connection up "{{item.name}}"
loop: "{{interfaces}}"
kvm_configure_vm/vars/main.yml
複数作る場合は、インターフェースのnameの数だけ作成していく。
また、管理用のNWに対しては、ループから外すためにインターフェースの変数を別途指定してやる。
hostname: test-vmname-1
# modify else mgmtip_interfce
interfaces:
- name: enp1s0f0
ip: 3.3.3.3/24
gateway: 2.2.2.2
dns:
- 1.1.1.1
- b.b.b.b
- a.a.a.a
firewallzone: external
- name: enp1s0f2
ip: 1.1.1.1
firewallzone: internal
#
management_gw: 4.4.4.4
management_interface: enp1s0f1
実行
以下のようにコマンドを実行
- become-pass
- sudo用のパスワードを実行するために。
- vault-pass
- secrets.ymlに指定した内容を解凍する。
- -iで、インベントリファイルを指定。
- --private-keyで、秘密鍵を指定する。
sudo ansible-playbook -v kvm-create-vm.yml -u ユーザ名 --ask-become-pass -i inventory.yml --private-key="/home/jst-dito/.ssh/honban_id_rsa" --ask-vault-pass