Linux入门DAY23
Linux入门DAY23
练习题
你是某互联网公司的运维工程师,团队计划引入 Ansible 实现服务器集群的自动化管理(如批量部署服务、配置文件分发)。部署一套ansible实践环境,需要 1 台 Ansible 控制主机和 4 台被控主机,统一使用普通用户devops
进行操作,要求:
- 控制节点通过
devops
账户免密登录所有被控节点 - 所有节点的
devops
账户可免密提权至 root(满足 Ansible 执行管理员操作的需求) - 环境基于 CentOS 7,确保后续 Ansible 剧本可正常运行
环境规划
主机角色 | 主机名 | IP 地址 | 操作系统 |
---|---|---|---|
控制节点 | controller | 10.1.8.10 | CentOS 7 |
被控节点 1 | node1 | 10.1.8.11 | CentOS 7 |
被控节点 2 | node2 | 10.1.8.12 | CentOS 7 |
被控节点 3 | node3 | 10.1.8.13 | CentOS 7 |
被控节点 4 | node4 | 10.1.8.14 | CentOS 7 |
解题过程
- 添加devops账户
- 推送密钥
- 提权root
- 控制节点安装ansible
- 准备测试清单
- 准备ansible配置文件
- 测试
所有节点创建devops用户
sudo useradd devops
echo "devops:devops" | sudo chpasswd
配置sudo免密(所有节点)
echo "devops ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/devops
sudo chmod 440 /etc/sudoers.d/devops
控制节点生成SSH密钥
su - devops
ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa
配置SSH免密登录
for i in {1..4}; dossh-copy-id devops@node$i# 首次需要输入密码devops
done
# 包括控制节点自身
ssh-copy-id devops@controller
控制节点安装Ansible
sudo yum install -y epel-release
sudo yum install -y ansible
创建最小化inventory文件
cat > ~/hosts <<EOF
[nodes]
node1 ansible_host=10.1.8.11
node2 ansible_host=10.1.8.12
node3 ansible_host=10.1.8.13
node4 ansible_host=10.1.8.14[all:vars]
ansible_user=devops
ansible_ssh_private_key_file=/home/devops/.ssh/id_rsa
EOF
测试连接
ansible all -i ~/hosts -m ping
ansible nodes -i ~/hosts -m shell -a "sudo whoami"
Ansible
编写和运行 Playbook
环境准备
[phoenix@controller ~ 11:48:17]$ cat inventory
controller
node1
node2
node3
node4
Playbook 介绍
- playbook 是一个文本文件,作为执行剧本
- adhoc 命令可以作为一次性命令对一组主机运行一项简单的任务。不过,若要真正发挥Ansible的能力,需要使用功能 playbook
- Playbooks以yaml格式编写,通常以 yaml 和 yml 扩展名保存
- yaml格式只使用空格缩进,对于空格的数量没有强制要求
基本规则:
- 同一级别的元素,使用相同的缩进。
- 对于子项目,使用比父项目更多的缩进。
- 增加空白行,提高可读性
#编辑vim环境变量
[phoenix@controller ~ 11:52:35]$ vim ~/.vimrc#自动缩进
#十字定位
set ai ts=2
set cursorcolumn cursorline
Playbook 编写
# yaml格式起始行,一般不省略
---# Playbook中第一个play
# play具有属性:name,hosts,become,tasks,缩进一致
# name属性,用于简要描述play
- name: Enable intranet services# hosts属性,用于定义要在哪个受管理节点执行hosts: node1# tasks属性,用于描述play中任务,属性是列表格式tasks:# 第一个任务# 任务具有属性:涵name和模块名等。# name属性,用于简要描述任务- name: latest version of httpd and firewalld installed# 指明模块名,也就是要执行的任务yum:# 执行要操作的rpm包名称name:# rpm包名称是-开头的列表格式,或者逗号分隔的列表格式- httpd- firewalld# 定义软件包的状态,lastet代表升级为最新版本state: latest# 第二个任务- name: test html page is installed# copy模块,用于将content属性值写入到目标文件copy:content: "Welcome Laoma WebSite!\n"dest: /var/www/html/index.html# 第三个任务- name: firewalld enabled and running# service模块,用于启用并启动firewalld服务service:name: firewalldenabled: truestate: started# 第四个任务- name: firewalld permits access to httpd service# firewalld,用于放行http服务firewalld:service: httppermanent: truestate: enabledimmediate: yes# 第五个任务- name: httpd enabled and running# service模块,用于启用并启动httpd服务service:name: httpdenabled: truestate: started# Playbook中第二个play,-开头表示列表
- name: Test intranet web serverhosts: localhostbecome: notasks:- name: connect to intranet web server# uri模块,用于测试网站是否可以访问uri:url: http://node1return_content: yesstatus_code: 200# yaml格式结束行,一般省略
...
yaml字典
yaml中是以一组键值对的集合实现的,又称为映射
以缩进块的形式编写键值对集合
fruit list:type1: bananatype2: apple
Playbook 运行
#准备playbook剧本文件
[phoenix@controller ~ 16:25:00]$ cat playbook.yml
---
- name: test vars statement in playhosts: node1vars:users:- user_name: phoenix1home_path: /home/phoenix1- user_name: phoenix2home_path: /home/phoenix2tasks:- name: add user {{ users.0.username }}user:name: "{{ users.0.user_name }}"home: "{{ users.0.home_path }}"- name: debug {{ users[1].user_name}}debug:msg: "{{ users[1].user_name }}"...
运行
[phoenix@controller ~ 16:25:19]$ ansible-playbook playbook.yml
PLAY [test vars statement in play] *********************************************************TASK [Gathering Facts] *********************************************************************
ok: [node1]TASK [add user {{ users.0.username }}] *****************************************************
ok: [node1]TASK [debug phoenix2] **********************************************************************
ok: [node1] => {"msg": "phoenix2"
}PLAY RECAP *********************************************************************************
node1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
语法检查
当yaml文件出现错误,使用–syntax选项
[phoenix@controller ~ 18:01:55]$ ansible-playbook --syntax-check playbook.ymlplaybook: playbook.yml
空运行
空运行,是指模拟运行,并不是真正执行
[phoenix@controller ~ 18:01:59]$ ansible-playbook playbook.yaml -C
Playbook 提权
在playbook中指定关键字将覆盖/etc/ansible/ansible.cfg文件中的设置特权升级属性
-
remote_user,指定ssh用户
-
become,启用或禁用特权升级
-
become_method,启用特权升级的方法
-
become_user,特殊升级的帐户
--- 2 - name: test vars statement in play3 hosts: node1 4 remote_user: phoenix5 become: true 6 become_method: sudo7 become_user: root8 9 vars:10 users:11 - user_name: phoenix112 home_path: /home/phoenix113 - user_name: phoenix214 home_path: /home/phoenix215 tasks:
...
管理变量和事实
环境准备
[phoenix@controller ~ 18:16:20]$ cat inventory
controller
node1
node2
node3
node4[phoenix@controller ~ 18:16:29]$ cat ansible.cfg
[defaults]
remote_user = phoenix
inventory = ./inventory[privilege_escalation]
become = True
become_user = root
become_method = sudo
become_ask_pass = False
管理VARIABLES
ansible 利用变量来存储数据,以便在项目中重复引用,我们可以在playbook针对操作定义
变量命名规则
- 只能包含字母、数字和下划线(如包含空格、点、$符号都为非法变量名)
- 只能**以字母开头*
变量优先级
- Global scope:从命令行或 Ansible 配置设置的变量
- Play scope:在play和相关结构中设置的变量
- Host scope:由清单、事实(fact)收集或注册的任务,在主机组和个别主机上设置的变量
优先级从高到低顺序:Global -> Play -> Host
如果出现定义多个相同名称变量,最高级优先
变量引用
- 变量名称的引用必须用
{{}}
符号,ansible会将变量替换为其值 - 当变量用作值的第一元素时,变量引用必须使用引号
Global scope
#-m 指定模块
#-a 指定命令
#-e 传递全局变量 优先级最高
[phoenix@controller ~ 18:26:43]$ ansible node1 -m yum -a "name={{ package }} state=present" -e "package=tree"
node1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "msg": "", "rc": 0, "results": ["tree-1.6.0-10.el7.x86_64 providing tree is already installed"]
}
Play scope
#play scope 作用于当前 play 的所有 tasks--- 2 - name: test vars statement in play3 hosts: node14 vars:5 user: user116 home: /home/user117 tasks:8 - name: add user {{ user }}9 user:10 name: "{{ user }}"11 home: "{{ home }}"12 state: present13 14 - name: debug user15 debug:16 msg: |17 username is {{ user }}18 home is {{ home }}19 ...
当需要引用相当多的变量,可以对变量文件分类引用
[phoenix@controller ~ 18:39:35]$ mkdir vars
[phoenix@controller ~ 18:39:40]$ vim vars/config.ymluser: user1 home: /home/user1host: localhost #playbook声明
--- 2 - name: test vars statement in play3 hosts: node1 4 vars: 5 users: 6 - user_name: phoenix1 7 home_path: /home/phoenix18 - user_name: phoenix2 9 home_path: /home/phoenix210 vars_files: 11 - vars/config.yml
Host scope
主机变量应用与主机和主机组,主机变量优先级高于组变量
主机清单中定义
#声明父组子组
#node1 node2 webs子组
#node3 node4 dbs子组
[phoenix@controller ~ 18:45:25]$ vim inventorycontroller2 [nodes:children] 3 webs 4 dbs 5 6 [webs] 7 node1 8 node2 9 10 [dbs] 11 node3 12 node4 #验证[phoenix@controller ~ 18:54:34]$ ansible all -m debug -a 'var=node'
controller | SUCCESS => {"node": "VARIABLE IS NOT DEFINED!"
}
node1 | SUCCESS => {"node": "NODE1"
}
node2 | SUCCESS => {"node": "NODE2"
}
node3 | SUCCESS => {"node": "NODES"
}
node4 | SUCCESS => {"node": "NODES"
}
目录分层结构定义
在项目目录中创建如下目录:
- group_vars,定义主机组变量
- host_vars,定义主机变量
[phoenix@controller ~ 18:59:05]$ cat inventory
controller
[nodes:children]
webs
dbs[webs]
node1
node2[dbs]
node3
node4
验证
var参数不需要引号引用
#node1所属主机组
[phoenix@controller ~ 19:09:57]$ ansible node1 -m debug -a var=group_names
node1 | SUCCESS => {"group_names": ["nodes", "webs"]
}#清单中所有主机组
[phoenix@controller ~ 19:10:02]$ ansible node1 -m debug -a var=groups
node1 | SUCCESS => {"groups": {"all": ["controller", "node1", "node2", "node3", "node4"], "dbs": ["node3", "node4"], "nodes": ["node1", "node2", "node3", "node4"], "ungrouped": ["controller"], "webs": ["node1", "node2"]}
}#特定主机组
[phoenix@controller ~ 19:12:49]$ ansible node1 -m debug -a var=groups.dbs
node1 | SUCCESS => {"groups.dbs": ["node3", "node4"]
}#查询所有主机变量信息
[phoenix@controller ~ 19:12:50]$ ansible node1 -m debug -a var=hostvars
主机连接特殊变量
参数名称 | 类型 | 默认值 | 描述 | 安全建议 |
---|---|---|---|---|
ansible_connection | string | smart | 连接协议类型: • smart (自动选择) • ssh • paramiko (Python SSH库) | 生产环境建议明确指定 ssh |
ansible_host | string | inventory主机名 | 实际连接的主机地址/IP | 优先使用DNS而非IP |
ansible_port | int | 22 | SSH端口号 | 非22端口需同步调整防火墙 |
ansible_user | string | 当前用户 | SSH登录用户名 | 建议专用运维账户 |
ansible_ssh_pass | string | - | SSH密码 | ❌ 必须用Ansible Vault加密 |
ansible_ssh_private_key_file | path | ~/.ssh/id_rsa | SSH私钥路径 | 权限应设为 600 |
ansible_ssh_common_args | string | - | 全局SSH参数(影响scp/sftp/ssh) | 示例:-o ProxyJump=bastion |
ansible_sftp_extra_args | string | - | SFTP额外参数 | 如带宽限制:-l 10240 |
ansible_scp_extra_args | string | - | SCP额外参数 | 如压缩传输:-C |
ansible_ssh_extra_args | string | - | SSH额外参数 | 如禁用DNS检查:-o StrictHostKeyChecking=no |
ansible_become | bool | false | 是否提权 | 等价于旧版 ansible_sudo |
ansible_become_method | string | sudo | 提权方式: • sudo • su • doas 等 | 需目标系统支持 |
ansible_become_user | string | root | 目标提权用户 | 非root用户需配置sudo权限 |
ansible_become_pass | string | - | 提权密码 | ❌ 必须用Ansible Vault加密 |
数组变量
通过使用[‘’]引用方式可以避免与python功能函数重名冲突
#示例
---
- name: test vars statement in playhosts: node1vars:users:- user_name: phoenix1home_path: /home/phoenix1- user_name: phoenix2home_path: /home/phoenix2tasks:- name: add user {{ users.0.username }}user:name: "{{ users.0.user_name }}"home: "{{ users.0.home_path }}"- name: debug {{ users[1].user_name}}debug:msg: "{{ users[1].user_name }}"...
register 语句
**register 语句捕获任务输出。**输出保存在一个临时变量中,稍后在playbook中可用于调试用途或者达成其他目的
[phoenix@controller ~ 19:18:26]$ cat playbook1.yml
---
- name: Installs a package and prints the resulthosts: node1tasks:- name: Install the packageyum:name: httpdstate: installedregister: install_result- debug: var: install_result
...
MAGIC 变量
magic 变量由 Ansible 自动设置,可用于获取与特定受管主机相关的信息
#当前环境
[phoenix@controller ~ 19:23:58]$ cat inventory
controller[webs]
node1
node2[dbs]
node3
node4
-
inventory_hostname包含配置中当前受管主机的主机名称
[phoenix@controller ~ 19:24:00]$ ansible node1 -m debug -a var=inventory_hostname node1 | SUCCESS => {"inventory_hostname": "node1" }
-
group_names列出当前受管主机所属的所有主机组
[phoenix@controller ~ 19:26:42]$ ansible node1 -m debug -a var=group_names node1 | SUCCESS => {"group_names": ["webs"] }
-
groups,列出清单中的所有组,以及组中含有的主机。
[phoenix@controller ~ 19:26:43]$ ansible node1 -m debug -a var=groups node1 | SUCCESS => {"groups": {"all": ["controller", "node1", "node2", "node3", "node4"], "dbs": ["node3", "node4"], "ungrouped": ["controller"], "webs": ["node1", "node2"]} }
-
hostvars,包含所有受管主机的变量,可用于获取另一台受管主机的变量的值
[phoenix@controller ~ 19:31:05]$ ansible node1 -m debug -a var=hostvars | head
node1 | SUCCESS => {"hostvars": {"controller": {"ansible_check_mode": false, "ansible_diff_mode": false, "ansible_facts": {}, "ansible_forks": 5, "ansible_inventory_sources": ["/home/phoenix/inventory"],
需求 在node1主机上获取node2所属主机组
[phoenix@controller ~ 19:32:57]$ ansible node1 -m debug -a var=hostvars.node2.group_names
node1 | SUCCESS => {"hostvars.node2.group_names": ["nodes", "webs"]
}
管理 SECRETS
ansible-vault
在ansible传递参数过程中,密钥是以纯文本明文方式显示,这样存在安全风险,需要用到ansible vault加密数据
ansible-vault 命令
命令 | 作用 |
---|---|
create | 新建一个由 Ansible Vault 加密的文件(默认使用 AES256 加密) |
decrypt | 将加密文件解密为明文(需提供加密时的密码) |
edit | 直接编辑加密文件(保存后自动重新加密) |
view | 查看加密文件内容(不修改文件) |
encrypt | 对普通 YAML 文件进行加密 |
encrypt_string | 直接加密字符串(用于嵌入 Playbook 或变量文件) |
rekey | 修改加密文件的密码(需提供旧密码和新密码) |
**示例 **
#create加密文件
[phoenix@controller ~ 16:02:59]$ ansible-vault create user.yaml
New Vault password:
Confirm New Vault password: #加密后cat命令是以密文方式显示
[phoenix@controller ~ 16:07:09]$ cat user.yaml
$ANSIBLE_VAULT;1.1;AES256
38313530396533323730336665316265386262346163623038663237326130343436326662353732
3539663339386335626537366339623730366130613939380a333837373666303132663736613962
35626263356364333735303634303034393631336365346430636462653330653061386334393537
3966666637663036300a343937303138616164323261623534633735326437383432646466333333
36323031643930653261643836653939336437633764373631326437323161303231383161626163
39366464626638306336353034383434373837313537643564636266346362356264663836663863
62623964656434366563663935333231366165393763393237363365316164313036306561353237
30626431646232376530#使用view参数 输入密码后显示明文
[phoenix@controller ~ 16:07:16]$ ansible-vault view user.yaml
Vault password:
login_name: phoenix
login_passwd: 1
login_host: 10.1.8.11
login_type: ssh#解密
[phoenix@controller ~ 16:07:32]$ ansible-vault decrypt user.yaml
Vault password:
Decryption successful#cat明文
[phoenix@controller ~ 16:07:51]$ cat user.yaml
login_name: phoenix
login_passwd: 1
login_host: 10.1.8.11
login_type: ssh#再次加密
[phoenix@controller ~ 16:07:55]$ ansible-vault encrypt user.yaml
New Vault password:
Confirm New Vault password:
Encryption successful
[phoenix@controller ~ 16:08:20]$ cat user.yaml
$ANSIBLE_VAULT;1.1;AES256
37666337373634346165393663383032613931636139656535633562373838393764326235643261
3663663361353463343866643762383539373039396564650a393030616130343332306466303338
38653737626666613362313265643962643865373365663761626134366232363731386565356232
3035653233313065610a623263326436656436303831313461636139613463383835383261656532
35373033646137383137333236663333333366613436356131393565353864373262323933656132
35623964303331666131323636356661633633326336303539363332366563343732306439366232
31316435626661353139653338343563356436303137646237633535613838386566623231316430
31323630643839393638#更改密码
[phoenix@controller ~ 16:08:27]$ ansible-vault rekey user.yaml
Vault password:
New Vault password:
Confirm New Vault password:
Rekey successful