说明
ansible是一个常用的运维管理工具,使用它可以避免很多重复性工作,节省大量时间。
这里是IT技术快速入门学院演示视频中使用的文档,可以在系列教程中找到该系列所有文章。
QQ交流群(ansible实践互助):955105412。
一句话原理
ansible就是把你手动ssh登录到多个目标机器上进行的一系列操作的过程自动化。
你只需要确保执行ansible命令的本地机器能够通过用户名和密码登录到目标机器上,并且在本地机器上的ansible文件中写好要在目标机器上执行的操作。
目标机器只需要支持ssh登录和python命令(一般的linux操作系统都有,ansible会将python写的任务脚本上传到目标机器上执行)。
文档介绍
ansible的文档首页 https://docs.ansible.com/ 对文档进行了分类,都是接到了文档内容页面。
安装文档中介绍了ansible的安装方法,作为一个很基础的工具,基本上每个操作系统,都有对应的安装方法。
官方的Getting Started介绍的太简单了,对初学者来说,看完还是一头雾水。
下载素材
git clone https://github.com/lijiaocn/ansible-example.git
两个命令: ansible 与 ansible-playbook
ansible有两个命令,一个是ansible
,一个是ansible-playbook
,前者需要每次输入要执行的命令,后者可以读取playbook
文件,一次性完成playbook文件中指定一系列操作。
playbook文件是重点,文档中有很大篇幅是介绍playbook的:playbook。
用ansible命令操作目标机器
准备hosts文件
需要准备一个文件,在文件中写下目标机器的地址,这个文件默认是/etc/ansible/hosts
,但是为了管理方便,最好为每个环境单独创建一个hosts文件。
比方说创建一个名为inventories
的目录,在这个目录下,为生产环境的机器创建一个production
目录,production/hosts
中记录的是生产环境中的机器的地址,demo/hosts
中记录的是演示环境中机器的地址,这样将不同环境中的机器明确地分开了,可以减少运维事故。
$ tree inventories/ inventories/ ├── production │ └── hosts └── demo └── hosts
hosts
文件中可以直接是目标机器的地址,可以是IP,也可以是域名,每个地址占用一行,例如:
192.168.33.11 www.baidu.com
如果目标集群中的机器的角色相同,承担的是同样任务,这种方式一般也足够了。如果目标集群中的机器分别承担不同任务,最好将它们按照各自的角色分组,例如:
[master]192.168.33.11[nodes]192.168.33.11192.168.33.12192.168.33.13
同一个地址,可以同时位于多个组中。
可以对分组再次分组,例如《Kubernetes1.12从零开始》中使用的hosts文件是这样的:
[etcd]192.168.33.11192.168.33.12192.168.33.13[master]192.168.33.11192.168.33.12192.168.33.13[node]192.168.33.11192.168.33.12192.168.33.13[kube-router]192.168.33.11192.168.33.12192.168.33.13############# group's group ##############[etcd_client:children]etcdmaster[etcd_server:children]etcd[etcd_peer:children]etcd[apiserver:children]master[controller:children]master[scheduler:children]master[kubelet_client:children]master[kubelet:children]node
名称里有:children
的分组,是分组的分组,它的成员是前面定义的分组。
还可以在这里为每个机器
设置变量,譬如《HyperLedger Fabric手把手入门》中使用的hosts文件:
[orderer]orderer0.member1.example.com MSPID=orderers.member1.example.com ORG_DOMAIN=member1.example.com ansible_host=192.168.33.11[peer]peer0.member1.example.com MSPID=peers.member1.example.com ORG_DOMAIN=member1.example.com ansible_host=192.168.33.11 STATE_DB=CouchDB COUCH_USER=admin COUCH_PASS=passwordpeer1.member1.example.com MSPID=peers.member1.example.com ORG_DOMAIN=member1.example.com ansible_host=192.168.33.12 STATE_DB=CouchDB COUCH_USER=admin COUCH_PASS=passwordpeer0.member2.example.com MSPID=peers.member2.example.com ORG_DOMAIN=member2.example.com ansible_host=192.168.33.13 STATE_DB=CouchDB COUCH_USER=admin COUCH_PASS=password[machine]192.168.33.11192.168.33.12192.168.33.13
你已经注意到了,这个hosts文件不太一样,地址后面多出了一些诸如MSPID=XXX
样式的内容,它们是为对应机器设置的变量,这些变量在可以在后面要讲的playbook文件中引用。
分组和变量的使用方法在后面演示,现在你先记得有这么一回事就行。
另外关于分组还要多说一句,ansible有两个默认的分组:all
和ungrouped
:all分组包括所有分组的中的机器,ungrouped是所有只属于all分组,不属于其它分组的机器。 在定义你自己的分组的时候,要注意分组名称不要与它们冲突。
讲述这部分内容的官方文档是:Working with Inventory
使用modules开始操作
Modules是ansible的“军火库”,几乎所有的操作功能都是用module实现的。
ansible用到最后,就是在使用module。 module的数量相当多,好在常用的就那么几个,这里演示一些常用的,其它的你可以通过每个module的文档学习。
ping模块是用来测试目标机器是否可达的,用法如下:
lijiaos-mbp:example lijiao$ ansible -i inventories/demo/hosts -u root -k all -m ping SSH password: 192.168.33.12 | SUCCESS => { "changed": false, "ping": "pong"}192.168.33.11 | SUCCESS => { "changed": false, "ping": "pong"}
-i
指定hosts文件,-u
指定目标机器上的用户名,-k
指定目标机器登录密码,all
是要操作的hosts文件中的分组,前面我们说过,all是默认存在的一个分组,包括所有机器,-m
指定要使用的模块ping
。
ping模块大概是最简单的一个模块,没有参数,再来看一个复杂一点的模块shell,它的功能是在目标机器上执行shell命令:
lijiaos-mbp:example lijiao$ ansible -i inventories/demo/hosts -u root -k all -m shell -a "hostname"SSH password: 192.168.33.11 | SUCCESS | rc=0 >>192.168.33.11 192.168.33.12 | SUCCESS | rc=0 >>192.168.33.12
-a
是指定传递给模块的参数。
用ansible
命令对目标机器操作时,都是在命令行指定要做的操作,一般都是一些比较简单操作,譬如查看下状态、上传下载文件等。
很多强大的功能要通过ansible-playbook
才能发挥出来。
用ansible-playbook命令操作目标机器
playbooks是yml格式的文件,描述了要在哪些机器上执行哪些操作。
在目标机器上创建一个文件
创建一个playbook文件,playbook-single.yml,如下:
- hosts: machines remote_user: root tasks: - name: create a tmp file shell: | cd /tmp/ touch abcd123
这个playbook文件的意思是,在所有的machines
上,用root的身份执行,并通过shell模块创建文件/tmp/abcd123,用法如下:
lijiaos-mbp:example lijiao$ ansible-playbook -i inventories/demo/hosts -k playbook-single.yml SSH password: PLAY [machines] ******************************************************************************TASK [Gathering Facts] ***********************************************************************ok: [192.168.33.12] ok: [192.168.33.11] TASK [create a tmp file] *********************************************************************changed: [192.168.33.12] changed: [192.168.33.11] PLAY RECAP ***********************************************************************************192.168.33.11 : ok=2 changed=1 unreachable=0 failed=0 192.168.33.12 : ok=2 changed=1 unreachable=0 failed=0
注意这里使用ansible-playbook
命令,-i
和-k
参数含义与前面ansible命令的参数相同,这里没有使用-u
指定账号,是因为在playbook-single.yml中已经设置了使用root:
remote_user: root
操作在playbook文件的tasks
中设置,tasks是一个数组,可以添加多个任务:
tasks: - name: create a tmp file # 自定义的操作名称 shell: | # 使用shell模块,后面的|是yaml语法,表示后面空行之前的内容都是shell模块的参数 cd /tmp/ touch abcd123
用ansible命令来看一下文件是否创建:
lijiaos-mbp:example lijiao$ ansible -i inventories/demo/hosts -u root -k all -m shell -a "ls /tmp/abc*"SSH password: 192.168.33.11 | SUCCESS | rc=0 >>/tmp/abcd123 192.168.33.12 | SUCCESS | rc=0 >>/tmp/abcd123
将操作以role为单位进行分组
前面给出的ansible-playbook的用法,是最初级的用法,比较完整的用法是将操作封装到role中。
先解释一下什么是role,为什么要有role。
在ansible看来role就是对playbook中的操作做了一次分组,把一些操作放在这个role中,另一些操作放在那个role中。
在我们看来,role是目标机器的角色之一,我们把不同的角色的操作划分到不同的目录中,一是管理方便,二是可以复用。
role要在roles
目录中定义,在roles目录中创建与role同名的目录,每个role目录中包含四个目录:
lijiaos-mbp:example lijiao$ tree roles/ roles/ └── prepare ├── files │ └── demo.file ├── handlers │ └── main.yml │ └── centos.yml ├── tasks │ └── main.yml └── templates └── demo.template.j2
tasks
目录中的main.yml
是这个role的操作入口,handlers/main.yml
中是一些可以被触发
的操作,files
中存放可以直接被上传到目标机器的文件,templates
中存放的是可以直接上传到目标机器的模版文件,这两个的区别后面说明。
注意tasks/main.yml
是必须要有的,其它目录中如果没有文件,可以不创建。
上面的目录中创建了一个名为prepare
的role,我们计划将机器的初始化设置操作全部在收集在这个role中,task/main.yml是这样写的:
- name: Set authorized key tags: ssh authorized_key: user: root key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" - name: Set hostname hostname: name: "{{ inventory_hostname }}" - name: Set bash prompt shell: | echo 'export PS1="[\u@\H \W]\\$ "'>> ~/.bashrc - name: install dependent packages import_tasks: centos.yml when: ansible_distribution == "CentOS"
用到了authorized_key、hostname、shell和import_tasks四个模块。
当目标机器的操作系统是ansible的时候,import_tasks
引入了centos.yml
文件:
- name: set time zone file: src: '{{ item.src }}' dest: '{{ item.dest }}' state: link with_items: - { src: "/usr/share/zoneinfo/Asia/Shanghai", dest: "/etc/localtime" } - name: set local shell: localedef -i zh_CN -f UTF-8 zh_CN.UTF-8 - name: install epel yum: name: "{{ item }}" state: present with_items: - epel-release - name: install pkgs yum: name: "{{ item }}" state: present with_items: - yum-utils - ipset - iptables - iproute - ipvsadm - supervisor - ntp - name: start basic service systemd: enabled: yes name: "{{ item }}" state: started with_items: - ntpd - supervisord
这些操作的含义在后面章节逐一说明,先给出用法:
ansible-playbook -i inventories/demo/hosts -u root -k prepare.yml
常用的目标机器初始化操作
这里介绍role/prepare/task/main.yml文件中的操作。
设置免密码登录
前面的操作过程中使用了-k
参数,每次都需要输入密码,一是比较烦,二是如果机器的密码不同,那就失灵了(后面会演示一下如果目标机器密码不同该怎样操作)。
最好把本地的证书传到目标机器上,实现免密码登录,prepare的task/main.yml中,有这样一段:
- name: Set authorized key tags: ssh authorized_key: user: root key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
它就是用authorized_key模块将本地的证书~/.ssh/id_rsa.pub
上传到目标机器上,实现免密码登录。
注意你需要确保你本地有id_rsa.pub文件,否则用ssh-keygen
命令创建一个:
$ ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/Users/lijiao/.ssh/id_rsa):
设置目标机器的hostname
- name: Set hostname hostname: name: "{{ inventory_hostname }}" - name: Set bash prompt shell: | echo 'export PS1="[\u@\H \W]\\$ "'>> ~/.bashrc
设置目标机器的时区
- name: set time zone file: src: '{{ item.src }}' dest: '{{ item.dest }}' state: link with_items: - { src: "/usr/share/zoneinfo/Asia/Shanghai", dest: "/etc/localtime" }
用yum安装依赖包
- name: install epel yum: name: "{{ item }}" state: present with_items: - epel-release - name: install pkgs yum: name: "{{ item }}" state: present with_items: - yum-utils - ipset - iptables - iproute - ipvsadm - supervisor - ntp
用systemd启动服务
- name: start basic service systemd: enabled: yes name: "{{ item }}" state: started with_items: - ntpd - supervisord
变量、文件、模版与Handler
这里通过在目标机器上部署、设置nginx,讲解角色下面的files、templates和handlers目录的作用。
nginx
role的文件如下:
lijiaos-mbp:example lijiao$ tree roles/nginx/ roles/nginx/ ├── files │ ├── start.sh │ └── stop.sh ├── handlers │ └── main.yml ├── tasks │ └── main.yml └── templates └── hello.com.conf.j2
变量的定义和引用
nginx/tasks/main.yml
内容是:
- name: install pkgs yum: name: "{{ item }}" state: present with_items: - nginx - name: nginx is running systemd: name: nginx state: started daemon_reload: yes - name: create directory file: path: "{{ item }}" state: directory with_items: - "{{ nginx_config_path }}" - "{{ nginx_script_path }}" - name: upload template config notify: reload nginx template: src: "{{ item }}.j2" dest: "{{ nginx_config_path }}/{{ item }}" with_items: - hello.com.conf - name: upload files copy: src: "{{ item }}" dest: "{{ nginx_script_path }}/{{ item }}" mode: u=rwx with_items: - start.sh - stop.sh
这里有两个变量:nginx_config_path
和nginx_script_path
,用两个大括号包裹引用。
它们是在inventories/demo/group_vars/all
中定义的:
nginx_config_path: /etc/nginx/conf.d nginx_script_path: /root/nginx
变量除了可以在group_vars
和host_vars
目录中定义,还可以在hosts文件中定义:
[machines] 192.168.33.11 port=8001 192.168.33.12 port=8002
以及在playbook文件中定义,回想一下我们用到的第一个playbook,里面有vars
:
$ cat playbook-single.yml - hosts: machines vars: http_port: 80 max_clients: 200 remote_user: root tasks: - name: create a tmp file shell: | cd /tmp/ touch abcd123
模版上传
role/nginx/templates/hello.com.conf.j2
是一个模版文件: ,模版文件中可以使用变量:
server { listen {{ port }}; location / { proxy_pass https://www.baidu.com ; } }
模版文件中可以使用变量,这里使用的变量port
是在hosts文件中定义的,可以为每个机器定义不同的端口:
[machines] 192.168.33.11 port=8001 192.168.33.12 port=8002
它们被用template模块上传,上传时会将模版文件中的变量换成变量的值,如下:
- name: upload template config notify: reload nginx template: src: "{{ item }}.j2" dest: "{{ nginx_config_path }}/{{ item }}" with_items: - hello.com.conf
文件上传
role/nginx/files
中的文件,用COPY命令上传,文件不会被做任何改动,这一点和templates显著不同:
- name: upload files copy: src: "{{ item }}" dest: "{{ nginx_script_path }}/{{ item }}" mode: u=rwx with_items: - start.sh - stop.sh
handler的触发
在tasks中,用notify
命令触发handler的执行:
- name: upload template config notify: reload nginx template: src: "{{ item }}.j2" dest: "{{ nginx_config_path }}/{{ item }}" with_items: - hello.com.conf
只有被触发的handler才会运行,并且是在所有的task之后运行。
如果有多个handler被触发,按照它们在handlers/main.yml中出现的顺序执行。
什么时候要用handler?
譬如说,配置文件被更新以后,需要重启或者重新加载的服务,这时候就可以在更新配置文件的task中,使用notify触发handler。