使用 Ansible 自动化您的编码环境,并仅使用 bash 脚本为其创建一个简单的 GUI
Ansible 简介
安装 Ansible
定义主机
剧本和角色
角色和剧本示例
从 CLI 运行 Ansible
为 Ansible 制作一个简单的 GUI
结论
我承认,我曾两次彻底毁掉我的 Linux 开发环境,这可不是件光荣的事。第一次出dd
了问题——没错,当时我正试图将一些固件写入连接到 USB 的微控制器,结果却犯了人们在使用 dd 命令时警告的那件事:不小心覆盖了我电脑的启动盘。
第二次又是自作自受:一个脚本出错,创建了一个名为'~'
“full”的文件夹,里面全是本不该存在的文件(其实你可以创建一个名为“full”的文件夹,不过需要用引号括起来)。我不假思索地用 删除了这个文件夹rm -rf ~
,结果我的整个主文件夹,包括所有设置和其他一些应用程序都被清空了。
虽然我的代码没问题——所有代码项目都保存在 git 中,但我的软件安装和开发环境却不行。我仍然不得不花几个小时重新设置所有程序。
从那时起,我决定自动设置我的开发环境,以便如果(或者更确切地说,当)我设法再次破坏我的安装时,我可以轻松地重新安装我需要的一切。
事实上,我用来将代码部署到虚拟机、维护无头机以及同事的开发笔记本的工作环境的也是同样的技术栈。它对我来说非常有效,所以我希望它能帮助那些像我一样,在“dd
或”的rm -rf
框架下冒险却最终失败的人。
Ansible 简介
Ansible 是一个开源程序,基本上可以在远程机器上执行各种任务,从安装软件到复制配置文件,再到运行 shell 命令。
Ansible 比一堆可以随意使用的 bash 脚本要好,原因有几个:其中一个主要好处是 Ansible 捆绑了大量扩展,允许您以声明方式编写要完成的操作,它会找出实际需要运行的操作,同时跳过任何已经完成的操作(它主要是幂等的)。
另一个好处是,Ansible 只需安装在执行控制的本地计算机上,无需安装在被管理的远程计算机上。它只需要 SSH 连接和 Python。无需在目标计算机上安装 Ansible(它是无代理的)。
Ansible 的所有文件都是 YAML 格式的,这意味着它们都可以存储在 Git 仓库中,从而轻松保持脚本的更新,或者添加脚本,甚至进行协作。我已经向多个团队介绍了这种设置,他们都采用了共享 Ansible 仓库的理念,并将许多以前需要手动完成的任务自动化。
安装 Ansible
Ansible 只需安装在负责管理的本地机器上。您可以使用这台机器通过 SSH 访问其他机器并进行操作。如果您想设置当前所在的机器,Ansible 也可以连接到本地机器。
如果您使用的是 Ubuntu,则可以通过其 ppa 安装 ansible。我还安装了aptitude
,因为 Ansible 更喜欢使用它而不是 apt 或 apt-get(尽管它也可以使用 apt)。我还安装了dialog
,这是一个用于在命令行中创建交互式对话框的简单实用程序,稍后您将了解我这样做的原因。
我把所有这些都放在一个install_ansible.sh
文件中,保存在仓库中,以防我需要在新机器上运行它,而还没有设置 ansible
#!/bin/bash
# install ansible ppa
apt-add-repository ppa:ansible/ansible -y
# install stuff
apt update
apt install -y aptitude ansible dialog
定义主机
Ansible 使用一个 hosts 文件,其中包含您可能想要连接和管理的所有主机的列表。如果您只是使用 Ansible 管理自己的机器,那么只需要一个local
条目;但如果您想管理远程机器,则可以在此文件中添加它们的 SSH 连接详细信息hosts.yml
。这里还有更多可用选项,包括特定的连接选项。有关这些详细信息,请参阅 Ansible 的文档。
all:
hosts:
local:
ansible_host: 127.0.0.1
ansible_connection: local
wintel0:
ansible_host: 192.168.1.121
wintel1:
ansible_host: 192.168.1.142
ybox-vm:
ansible_host: 192.168.1.128
最后三个是我想要管理的几台远程机器。
剧本和角色
Ansible 的主要“工作单元”是 playbook。这是一个单独的 YAML 文件。我喜欢为每台需要保持设置或更新的物理机器保留至少一个 playbook。这样,每当我想确保某台机器拥有最新的基础软件时,我就可以运行它的 playbook。有时,当集群中的所有机器都需要相同的设置时,可以将主机组添加到 hosts.yml 中,并使用一个针对所有机器的 playbook。
Ansible 还有另一个层级结构,称为“角色”。我喜欢用角色来表示某个 playbook 需要执行的具体操作,例如设置某个软件包。
例如,我有一套经常在机器上使用的软件,所以我设置了几个角色来设置它们,并且可以将这些角色添加到这些剧本中。我始终运行的第一个剧本名为:,software-common-apt
它会设置我在进行任何远程管理工作时需要用到的各种软件:vim
,,tmux
。htop
然后,我有一个software-python
脚本来确保我拥有正确版本的 Python、pip、setuptools 和 Wheel。Ansible 实际上需要先安装 Python,但大多数 Linux 发行版都自带了 Python。所以我的 Python 角色主要是确保 pip 和一些常用软件包可用。
最后,我有一个software-docker
安装包,它完成了安装 docker、docker-compose 和 docker-credential-helper 所需的所有步骤。我几乎所有的工作都涉及到 docker,所以我使用的每台机器上都会安装这个包。
角色和剧本示例
例如,该文件playbooks/roles/software-common-apt/tasks/main.yml
如下所示:
---
- name: Installing Common Apt packages
become: yes
become_method: sudo
apt:
pkg:
- vim
- tmux
- htop
- jq
很简单。文件playbooks/roles/software-docker/tasks/main.yml
看起来像这样:
---
- name: Install packages
become: yes
become_method: sudo
block:
- name: Ensure built-in docker is removed
apt:
pkg:
- docker
- docker-engine
- docker.io
state: absent
- name: Install docker GPG
apt_key:
id: 0EBFCD88
url: https://download.docker.com/linux/ubuntu/gpg
state: present
- name: Install docker apt repository
apt_repository:
repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
update_cache: yes
- name: Install docker-ce
apt:
name: docker-ce
state: latest
- name: Install dependencies
apt:
pkg:
- curl
- apt-transport-https
- ca-certificates
- software-properties-common
- name: Install Python docker package
pip:
name:
- docker
- name: Check if docker-compose is installed
stat:
path: "{{ compose_path }}"
register: compose_is_installed
- name: Check docker-compose version
when: "compose_is_installed.stat.exists"
command: "{{ compose_path }} version"
register: compose_read_version
changed_when: False
failed_when: False
- name: Install docker-compose
when: "not compose_is_installed.stat.exists or compose_version not in compose_read_version.stdout"
get_url:
url: "https://github.com/docker/compose/releases/download/{{ compose_version }}/docker-compose-Linux-x86_64"
dest: "{{ compose_path }}"
owner: root
group: root
mode: +x
- name: Check if docker-credential-helper is installed
stat:
path: "{{ credential_path }}"
register: credential_is_installed
- name: Check docker-credential-helper version
when: "credential_is_installed.stat.exists"
command: "{{ credential_path }} version"
register: credential_read_version
changed_when: False
failed_when: False
- name: Install docker-credential-helpers
when: "not credential_is_installed.stat.exists or credential_version not in credential_read_version.stdout"
unarchive:
src: "https://github.com/docker/docker-credential-helpers/releases/download/v{{ credential_version }}/docker-credential-secretservice-v{{ credential_version }}-amd64.tar.gz"
remote_src: yes
dest: /usr/local/bin
mode: +x
- name: "Remember to add docker group to users!"
debug:
msg: Remember to add docker groups to users with "usermod -aG docker <username>". Use "newgrp docker" to use the group immediately
变量从哪里提取出来playbooks/roles/software-docker/vars/main.yml
,如下所示:
---
compose_version: "1.25.4"
compose_path: /usr/local/bin/docker-compose
credential_version: "0.6.3"
credential_path: /usr/local/bin/docker-credential-secretservice
这使我可以指定要安装的内容的确切版本,以确保安装的可重复性。
Ansible Roles 希望文件位于特定的文件夹结构中,请务必查看其文档以了解这些详细信息。
这些角色需要绑定到一个剧本中。所以我有一个playbooks/machine-wintel0.yml
脚本,用于在那台机器上安装所有需要的东西。它看起来像这样:
---
- hosts: wintel0
tasks:
- include_role:
name: software-common-apt
- include_role:
name: software-python
- include_role:
name: software-docker
请注意,此剧本是如何专门针对 的wintel0
。我也可以告诉此脚本以 为目标all
,运行它会尝试使我的 hosts 文件组(即所有内容)中的所有软件保持最新all
。如果您需要维护大量使用相同基础软件的机器,这有时会很有用。
从 CLI 运行 Ansible
有了这些文件并安装了 Ansible,我现在可以从命令行运行它。
$ ansible-playbook playbooks/machine-wintel0.yml -i hosts.yml -K --ask-pass
SSH password:
BECOME password[defaults to SSH password]:
PLAY [wintel0] ****************************************************
TASK [Gathering Facts] ****************************************************
ok: [wintel0]
TASK [include_role : software-common-apt] *********************************
TASK [software-common-apt : Installing Common Apt packages] ***************
ok: [wintel0]
TASK [include_role : software-python] *************************************
TASK [software-python : Installing Python 3.8] ****************************
ok: [wintel0]
TASK [software-python : Install stuff that ansible needs] *****************
ok: [wintel0]
TASK [include_role : software-docker] *************************************
TASK [software-docker : Ensure built-in docker is removed] ****************
ok: [wintel0]
TASK [software-docker : Install docker GPG] *******************************
ok: [wintel0]
TASK [software-docker : Install docker apt repository] ********************
ok: [wintel0]
TASK [software-docker : Install docker-ce] ********************************
ok: [wintel0]
TASK [software-docker : Install dependencies] *****************************
ok: [wintel0]
TASK [software-docker : Install Python docker package] ********************
ok: [wintel0]
TASK [software-docker : Check if docker-compose is installed] *************
ok: [wintel0]
TASK [software-docker : Check docker-compose version] *********************
ok: [wintel0]
TASK [software-docker : Install docker-compose] ***************************
skipping: [wintel0]
TASK [software-docker : Check if docker-credential-helper is installed] ***
ok: [wintel0]
TASK [software-docker : Check docker-credential-helper version] ***********
ok: [wintel0]
TASK [software-docker : Install docker-credential-helpers] ****************
ok: [wintel0]
TASK [software-docker : Remember to add docker group to users!] ***********
ok: [wintel0] => {
"msg": "Remember to add docker groups to users with \"usermod -aG docker <username>\". Use \"newgrp docker\" to use the group immediately"
}
PLAY RECAP ****************************************************************
wintel0: ok=16 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
在这种情况下,有很多ok
(因为我之前已经设置过这台机器)。这意味着特定任务已经完成,因此无需执行该任务。如果 Ansible 进行了任何更改,它会突出显示为changed
。这正是 Ansible 的优秀之处之一,因为它可以决定是否尝试运行某个任务。对于您定义的自定义任务,您还可以添加自己的方法来检测任务是否应该运行。
我还选择了--ask-pass -K
在运行命令时提示输入 SSH 密码和 sudo 密码。如果您配置了密钥访问和无密码 sudo,则无需提示输入密码。
为 Ansible 制作一个简单的 GUI
如你所见,所有角色和剧本都设置好了,无论何时我想运行它,只需发出命令即可。我最终得到了一份长长的剧本清单,其中包含不同的剧本,每个剧本都执行不同的任务。有些剧本只执行一些简单的操作,例如远程关闭机器(运行剧本比通过 SSH 登录然后发出关机命令更容易);有些剧本则用于获取日志文件。
这么一大堆 playbook 列表实在难以记住,大多数时候我得先浏览文件看看都有哪些,才能输入命令。为了解决这个问题,我使用了一个简单的 GUI 来查看我有哪些 playbook 并选择它们,有时我还想限制我的运行只在特定的主机上。这时 GUI 就派上用场了dialog
。不用再学习任何 GUI/TUI 库了,dialog
这是快速与 Bash 脚本交互的捷径。
我的脚本很简单:
#!/bin/bash
HEADER="Run Ansible Playbook"
TLINES=$(tput lines)
TCOLS=$(expr $(tput cols) \* 4 / 5)
# get playbook
FILE=$(dialog --backtitle "$HEADER" --stdout --clear --title "Select Playbook" --fselect playbooks/ $(expr $TLINES - 15) $TCOLS)
if [ ! -f "$FILE" ]; then
dialog --backtitle "$HEADER" --clear --title "Error" --msgbox "The file $FILE was not found" 5 $TCOLS
clear
exit 1
fi
# get hosts
# HOST_LIST=$(yq r hosts.yml -j | jq ".all.hosts|keys[]" | tr -d '"')
# HOST_NUM=$(echo "$HOST_LIST" | wc -l)
# HOST_STR=$(echo "$HOST_LIST" | awk '{print $0" off"}' | nl -w1 -s" " | tr "\n" " ")
# HOSTS=$(dialog --backtitle "$HEADER" --stdout --clear --title "Select Hosts" --checklist "Use space to select" $(expr $TLINES - 10) $TCOLS $HOST_NUM $HOST_STR) # the xagrs trims spaces
#
# CHECK=$(echo $HOSTS | tr -d "\n")
# if [ -z "$CHECK" ]; then
# dialog --backtitle "$HEADER" --clear --title "Error" --msgbox "No hosts selected, use space to select" 5 $TCOLS
# clear
# exit 1
# fi
#
# HOST_CSV=$(echo $HOSTS | tr " " "\n" | (while IFS=" " read -r line; do echo -n "$HOST_LIST" | sed -n "${line}p"; done;) | tr "\n" ",")
#
# override
HOST_CSV="all"
dialog --backtitle "$HEADER" --stdout --clear --title "Ask for SSH password?" --yesno "Yes: enter SSH password\nNo: local or key access available" 10 $TCOLS
RETVAL=$?
if [ $RETVAL -eq 0 ]; then
ASK_PASS="-K --ask-pass"
fi
clear
ansible-playbook $FILE -i hosts.yml --limit "$HOST_CSV" $ASK_PASS $@
运行此脚本将弹出一个文件选择对话框,让你选择要运行的剧本。它还会询问你将运行限制在哪个主机上(如果你取消注释掉脚本中的该部分);还会询问你是否要输入密码并尝试使用密钥文件。
这个选择对话框让 Ansible 的使用变得轻而易举,我只需启动./run_ansible.sh
(其中包含上述脚本,并且也位于 git 仓库中),选择我想要的 playbook,它就可以运行了。再也不用记住 playbook 的具体名称或主机名了。
此时的最终文件夹结构如下所示:
结论
多年来,我一直以这种方式维护我的个人开发机器和工作服务器部署,并逐渐建立了一个用于各种任务的剧本库。Ansible 完全取代了我以前为了提醒自己“如何安装 x”而记录的笔记,因为有些软件的安装过程比较难记。
对于我的 DevOps 任务来说,Ansible 与 CI/CD 和基础设施即代码 (Infrastructure-as-Code) 配合得很好。我们的代码库有时会有一个deploy
文件夹,其中包含一个 Ansible 剧本,用于在构建完成后由 CI/CD 运行;该剧本包含设置或更新特定服务器以及将代码或 Docker 镜像部署到该服务器所需的所有任务。别忘了,Github Actions 也预装了 Ansible,因此您可以立即从 Github Actions 开始部署任务,而无需先安装 Ansible。
我强烈推荐这种工具给那些厌倦了一直配置和维护开发环境或服务器的开发人员。
链接:https://dev.to/meseta/automate-your-coding-environment-with-ansible-and-make-a-simple-gui-for-it-using-only-bash-scripting-3742