说到SSH终端工具，大家首先想到的可能会是putty、xshell这些老牌客户端。以上这些工具能够流行至今，并有一定的用户群体，一定有其独特之处。不过在互联网日益发达的今天，我们工作中的使用需求也越来越多，比如：在远程连接服务器、运行Linux命令的同时，可能还需要传输一些文件；我们希望可以同时连接多台服务器，在多个窗口下管理，便于相互切换；在管理Linux的同时，还可以运行cmd/zsh以及gitbash，而不是开多个软件……一、windterm简介1.关于windtermwindterm是一款风格独特、开源、且完全免费的SSH/Telnet/Serial/Shell/Sftp 客户端项目地址：https://github.com/kingToolbox/WindTerm下载地址：https://github.com/kingToolbox/WindTerm/releases2.windterm特点 实现了 SSH v2、Telnet、Raw Tcp、串行、Shell 协议； 会话认证时支持 SSH 自动执行； 支持 SSH ControlMaster； 支持 SSH ProxyCommand 或 ProxyJump； 支持 SSH 代理转发； 支持使用密码、公钥、键盘交互、gssapi-with-mic 的 SSH 自动登录； 支持X11转发； 支持直接/本地端口转发、反向/远程端口转发和动态端口转发； 支持 XModem、YModem 和 ZModem； 集成sftp、scp客户端，支持下载、上传、删除、重命名、新建文件/目录等； 集成本地文件管理器，支持移动、复制、复制、删除、重命名、新建文件/目录等； 支持 Windows Cmd、PowerShell 和 Cmd、PowerShell 作为管理员； 支持Linux bash、zsh、powershell core等； 支持 MacOS bash、zsh、powershell 内核等； 二、创建服务器连接以下将会结合实际案例，分别从直连方式以及代理连接两方面，介绍windterm如何连接、管理远程服务器。1.本地或公网直连1）新建会话 主机：输入内网主机地址或可以直连的公网地址 标签：可以创建一个别名 分组：选择分组（分组可以提前新建，如百度云、华为云、内网服务器等），便于分类管理 Description：描述信息 2）输入用户名输入用户名，勾选“下次自动登录”，点击“继续”3）输入密码输入密码，点击“继续”登录成功页面如下：二、使用windterm1.主页布局通过截图可以看出： 右上角会话区域，可以新建服务器会话连接，支持新建、导入 左上角区域为已打开的会话 文件管理器支持文件的上传、下载，支持直接拖拽到此区域进行文件上传 大纲区域为此会话输入的历史命令，双击可立即在此会话窗口执行该命令 2.上传&下载文件左下角文件管理器，可以创建文件夹，上传、下载文件等，也可以将文件直接拖到这个区域进行上传文件、直接将文件从这个区域拖出进行下载文件。3.cmd & git bashwindterm右上角会话区域Shell sessions中集成了cmd及git bash窗口功能，便于同时操作多种类型会话。4.历史操作命令虽然有些执行过的历史命令可以通过history或是上下键进行翻滚查找到，但windterm在右下角大纲区域直接展示了本会话过程中所有执行过的历史命令，双击即可再次执行，免去多次重复键入或查找的麻烦。我认为这也是windterm最好用的特色功能之一。三.通过代理连接内网服务器或公网地址直连相对较为简单，直接输入用户名和密码就可以了。但很多情况下，并不是每台远程服务器都有公网地址，或是22端口不对外开放，这时就需要通过一台公网服务器的代理跳转到其同一体系的各个内网下才能进行连接。以下为代理连接核心思路：先建立一个隧道，用来监听本地端口，再新建一个代理连接，并在代理中填写前面创建的隧道中填写的本地监听端口号，通过【隧道+代理】即可跳转到远程服务器的内网其他主机，注意： 一个隧道中填写一个端口号，端口号只要是本地没被占用的端口号就行； 一个隧道最好只对应一个代理连接（避免因为一个端口挂掉而导致关联连接全断掉的情况）； 一个代理对应一个远程服务器的内网主机； 内网主机登录前，跳板机必须处于登录状态，跳板机断开后，相应各内网主机也会自动断连； 1）新建隧道点击右上角“隧道”-“Tunnel Settings”进入隧道管理，再点击右下角“+”号新建一个隧道，如下图所示： 名称：任意，自己能记住就行，最好带有一定标识来标识特定的含义 类型：选择动态转发 会话：选择跳板机所在的会话（需要通过这台跳板机、也是公网服务器来跳转到内网） 监听端口：任意，注意不要与其他已正在使用的端口重复即可 目标主机：选择动态转发后此项会自动填充为“任何”，无需改动 目标端口：选择动态转发后此项会自动填充为“0”，无需改动 Status：默认Closed，可以在新建时打开，也可以新建完成后打开 新建完成后，再次点击右上角隧道，即可看到新建的隧道：2）新建连接代理在同一会话（即跳板机所在的目录）下，右键-新建会话① 填写会话内容 主机：目标服务器的内网地址 端口：22 标签：任意，自己能识别即可 分组：跳板机所在的分组 其他保持默认 ② 填写代理信息切换到SSH类目下的代理选项，填写代理信息 代理类型：选择Socks v5 proxy 主机：localhost 端口：3011（前面新建的隧道中的本地监听端口，一个端口对应一个代理） 用户名密码无需填写 ③ 填写登录信息前面一步建立代理、点击连接后，会自动弹出输入用户名页面，可以勾选左下角的“下次自动登录”勾选后的效果如下，输入用户名即可输入密码登录后的页面如下：5.设置锁屏密码windterm最右下角“锁屏”可以设置锁屏密码，更改密码等，避免误操作，增强安全性。
#!/bin/bash # 脚本参数提示 function help_info(){ echo -e "\033[32m\t\t\t请在脚本后面输入参数\t\t\t\t\033[0m" echo -e "\033[32m+-----------------------+---------------------------------------+\033[0m" echo -e "\033[32m|\t参数\t\t|\t\t详解\t\t\t|\033[0m" echo -e "\033[32m+-----------------------+---------------------------------------+\033[0m" echo -e "\033[32m|\toffline\t\t|\t离线安装\t\t\t|\033[0m" echo -e "\033[32m|\tonline\t\t|\t在线安装(需要机器访问外网)\t|\033[0m" echo -e "\033[32m|\tremove\t\t|\t清理环境\t\t\t|\033[0m" echo -e "\033[32m|\thelp\t\t|\t帮助信息\t\t\t|\033[0m" echo -e "\033[32m|\tuninstall\t|\t卸载docker\t\t\t|\033[0m" echo -e "\033[32m+-----------------------+---------------------------------------+\033[0m" } # 安装其他docker版本 function download_other_version(){ cd /root/docker_install_pkg/ # 若第2个参数不做自定义版本要求，则默认使用24.0.2版本安装包 docker_version=${2:-24.0.2} while true; do wget https://download.docker.com/linux/static/stable/x86_64/docker-$docker_version.tgz &> /dev/null if [ $? -eq 0 ]; then echo -e "\033[32m下载Docker离线安装包为:docker-$docker_version.tgz,包存放路径为:/root/docker_install_pkg/\033[0m" break fi done # wget https://download.docker.com/linux/static/stable/x86_64/docker-$docker_version.tgz >> /dev/null # echo -e "\033[32m下载Docker离线安装包为:docker-$docker_version.tgz,包存放路径为:/root/docker_install_pkg/\033[0m" } # function find_pkg(){ cp -a $(find / -name 'docker-24.0.2.tgz' | grep -i docker) /root/docker_install_pkg/ } # 离线安装配置 function install_service(){ cd /root/docker_install_pkg/ tar -zxf docker*.tgz cp -p docker/* /usr/bin/ cat > /usr/lib/systemd/system/docker.service << EOF [Unit] Description=Docker Application Container Engine Documentation=http://docs.docker.com After=network.target docker.socket [Service] Type=notify EnvironmentFile=-/run/flannel/docker WorkingDirectory=/usr/local/bin ExecStart=/usr/bin/dockerd \ -H tcp://0.0.0.0:4243 \ -H unix:///var/run/docker.sock \ --selinux-enabled=false \ --log-opt max-size=1g ExecReload=/bin/kill -s HUP $MAINPID # Having non-zero Limit*s causes performance problems due to accounting overhead # in the kernel. We recommend using cgroups to do container-local accounting. LimitNOFILE=infinity LimitNPROC=infinity LimitCORE=infinity # Uncomment TasksMax if your systemd version supports it. # Only systemd 226 and above support this version. #TasksMax=infinity TimeoutStartSec=0 # set delegate yes so that systemd does not reset the cgroups of docker containers Delegate=yes # kill only the docker process, not all processes in the cgroup KillMode=process Restart=on-failure [Install] WantedBy=multi-user.target EOF mkdir -p /etc/docker cat > /etc/docker/daemon.json << EOF { "registry-mirrors": [ "https://2a6bf1988cb6428c877f723ec7530dbc.mirror.swr.myhuaweicloud.com", "https://docker.m.daocloud.io", "https://hub-mirror.c.163.com", "https://mirror.baidubce.com", "https://your_preferred_mirror", "https://dockerhub.icu", "https://docker.registry.cyou", "https://docker-cf.registry.cyou", "https://dockercf.jsdelivr.fyi", "https://docker.jsdelivr.fyi", "https://dockertest.jsdelivr.fyi", "https://mirror.aliyuncs.com", "https://dockerproxy.com", "https://mirror.baidubce.com", "https://docker.m.daocloud.io", "https://docker.nju.edu.cn", "https://docker.mirrors.sjtug.sjtu.edu.cn", "https://docker.mirrors.ustc.edu.cn", "https://mirror.iscas.ac.cn", "https://docker.rainbond.cc" ] } EOF systemctl daemon-reload systemctl start docker systemctl enable docker >> /dev/null echo -e "\033[32mDocker服务启动完成，并设置为开机自启!\033[0m" } function service_alive(){ systemctl is-active docker >> /dev/null if [ $? -eq 0 ];then echo -e "\033[31mDocker已经存在，无需安装!\033[0m" fi } #卸载docker function uninstall_docker(){ docker stop $(docker ps -aq) &> /dev/null docker rm $(docker ps -aq) &> /dev/null docker rmi $(docker images -q) &> /dev/null systemctl stop docker rm -rf /etc/systemd/system/docker.service /usr/bin/containerd /usr/bin/containerd-shim /usr/bin/ctr /usr/bin/runc /usr/bin/docker* /etc/docker/ /var/lib/docker echo -e "\033[32mDocker卸载完成!\033[0m" } # 安装执行 if [ `whoami` = 'root' ];then # 安装目录 mkdir -pv /root/docker_install_pkg/ >> /dev/null case $1 in offline ) service_alive # 离线安装 find_pkg && install_service ;; online ) service_alive # 在线安装 download_other_version && install_service ;; remove ) # 清理环境 if [ -d /root/docker_install_pkg/ ];then rm -r /root/docker_install_pkg/ echo -e "\033[32m环境清理完成!\033[0m" else echo -e "\033[31m环境清理完成，无需重复执行!\033[0m" fi ;; uninstall ) # 卸载docker uninstall_docker ;; * | help ) # 帮助 help_info ;; esac else echo -e "\033[31m当前执行用户为非root用户，推荐使用root用户执行安装操作!\033[0m" fi
已经用了很长时间了,也对比了多家,综合来看这家是性价比最高的,同时支持签到来延长使用周期,当然如果需要自动签到方法的可加群沟通。 平时翻阅文献和github下载、刷YT等速度刷刷的,总体来说挺稳定的支持多端同时在线,后续会持续使用。备注：要理性网上冲浪,合理合规浏览所需内容,切勿转发和讨论与IT技术无关的事情~推荐注册入口(以下三种方式任选)通过 https://glados.space/landing/2DF9O-3C3QB-766FS-YTHFS 注册, 自动填写激活通过 https://2df9o-3c3qb-766fs-ythfs.glados.space , 自动填写激活直接注册GLaDOS(注册地址在 https://github.com/glados-network/GLaDOS 实时更新)成功后输入邀请码:2DF9O-3C3QB-766FS-YTHFS 激活
1.背景又到了一年一度的ssl证书替换的时间了, 这次替换证书并没有和往常一样预期的那么丝滑， 这不刚替换了非生产环境的证书业务就开始反馈报错了， 自此期间也度娘和谷歌了一番,有说改java代码的、有说证书问题的、有说服务器问题的 各抒己见 下面贴下报错信息， 给有和我一样的童鞋做个借鉴 2. 报错信息org.springframework.web.client.ResourceAccessException: I/O error on POST request for "https://labelservice-dev.wangmanyuan.com/rest/label/condition": sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target; nested exception is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:674) at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:621) at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:415) at com.tal.file.business.client.LabelRestClient.condition(LabelRestClient.java:51) at com.tal.file.core.common.utils.CoursewareUtil.copyLabel(CoursewareUtil.java:2778) at com.tal.file.business.service.impl.CoursewareServiceImpl.packageOnCopy(CoursewareServiceImpl.java:1122) at com.tal.file.business.service.impl.CoursewareServiceImpl.copyCommon(CoursewareServiceImpl.java:329) at com.tal.file.business.service.impl.CoursewareServiceImpl.copyAsync(CoursewareServiceImpl.java:258) at com.tal.file.business.service.impl.CoursewareServiceImpl$$FastClassBySpringCGLIB$$b182efe0.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) 3.思考证书报错的原因清晰可见他们的牌子和加密算法是一样的 可是切换过去就报错 3.1证书解析过程3.2 HTTPS在传输的过程中会涉及到三个密钥：服务器端的公钥和私钥，用来进行非对称加密 客户端生成的随机密钥，用来进行对称加密 一个HTTPS请求实际上包含了两次HTTP传输，可以细分为8步 客户端发起HTTPS请求用户在浏览器里输入一个https网址，然后连接到server的443端口。 服务端的配置采用HTTPS协议的服务器必须要有一套数字证书，可以自己制作，也可以向组织申请。区别就是自己颁发的证书需要客户端验证通过，才可以继续访问，而使用受信任的公司申请的证书则不会弹出提示页面。这套证书其实就是一对公钥和私钥。如果对公钥和私钥不太理解，可以想象成一把钥匙和一个锁头，只是全世界只有你一个人有这把钥匙，你可以把锁头给别人，别人可以用这个锁把重要的东西锁起来，然后发给你，因为只有你一个人有这把钥匙，所以只有你才能看到被这把锁锁起来的东西。 传送证书这个证书其实就是公钥，只是包含了很多信息，如证书的颁发机构，过期时间等等。 客户端解析证书这部分工作是有客户端的TLS来完成的，首先会验证公钥是否有效，比如颁发机构，过期时间等等，如果发现异常，则会弹出一个警告框，提示证书存在问题。如果证书没有问题，那么就生成一个随即值。然后用证书对该随机值进行加密。就像上面说的，把随机值用锁头锁起来，这样除非有钥匙，不然看不到被锁住的内容。 客户端收到服务器端的证书之后，会对证书进行检查，验证其合法性，如果发现发现证书有问题，那么HTTPS传输就无法继续。严格的说，这里应该是验证服务器发送的数字证书的合法性。如果公钥合格，那么客户端会生成一个随机值，这个随机值就是用于进行对称加密的密钥，我们将该密钥称之为client key，即客户端密钥，这样在概念上和服务器端的密钥容易进行区分。然后用服务器的公钥对客户端密钥进行非对称加密，这样客户端密钥就变成密文了，至此，HTTPS中的第一次HTTP请求结束。 传送加密信息这部分传送的是用证书加密后的随机值，目的就是让服务端得到这个随机值，以后客户端和服务端的通信就可以通过这个随机值来进行加密解密了。 客户端会发起HTTPS中的第二个HTTP请求，将加密之后的客户端密钥发送给服务器。 服务段解密信息服务端用私钥解密后，得到了客户端传过来的随机值(私钥)，然后把内容通过该值进行对称加密。所谓对称加密就是，将信息和私钥通过某种算法混合在一起，这样除非知道私钥，不然无法获取内容，而正好客户端和服务端都知道这个私钥，所以只要加密算法够彪悍，私钥够复杂，数据就够安全。 传输加密后的信息这部分信息是服务段用私钥加密后的信息，可以在客户端被还原 客户端解密信息客户端用之前生成的私钥解密服务段传过来的信息，于是获取了解密后的内容。整个过程第三方即使监听到了数据，也束手无策。 这样HTTPS中的第二个HTTP请求结束，整个HTTPS传输完成。SSL的位置SSL介于应用层和TCP层之间。应用层数据不再直接传递给传输层，而是传递给SSL层，SSL层对从应用层收到的数据进行加密，并增加自己的SSL头。3.3 根证书每15年更换https://help.aliyun.com/document_detail/179033.htm?spm=0.2020520163.help.dexternal.3cb237119U1VUdhttps://help.aliyun.com/document_detail/610069.html3.4 思考当前运行环境我们现在运行的服务环境go、nodejs、python、c、java 从报错日志来看目前出现问题的是java+jdk1.7的组合下发生的问题 那么就开始从这里下手 目前根据阿里提供的新的根证书进行导入到jdk1.7内, 原则上导入后恢复，从实际现象来看是需要重启服务的才能生效 3.5 具体操作流程3.5.1 上传G2根证书可根据阿里给的文件进行下载,要是不知道的童鞋可以加群和我要下 3.5.2 确认jdk1.7所在服务器目录执行导入命令 echo yes|keytool -importcert -file Digicert-OV-DV-root.cer -alias DigicertWangManYuan20240717 -keystore /usr/local/jdk1.7.0_79/jre/lib/security/cacerts -storepass changeit 3.5.2 导入后测试在导入后发现有的服务重启后生效 有的却不生效 具体排查方式有一个demo文件可以进行测试 3.5.3 demo文件[root@wangmanyuan abc]# cat SSLPoke.java import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import java.io.*; public class SSLPoke { public static void main(String[] args) { if (args.length != 2) { System.out.println("Usage: "+SSLPoke.class.getName()+" <host> <port>"); System.exit(1); } try { SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket(args[0], Integer.parseInt(args[1])); InputStream in = sslsocket.getInputStream(); OutputStream out = sslsocket.getOutputStream(); out.write(1); while (in.available() > 0) { System.out.print(in.read()); } System.out.println("Successfully connected"); } catch (Exception exception) { exception.printStackTrace(); } } } 3.5.4 编译一下 执行编译 javac SSLPoke.java 编译后生成SSLPoke.class文件 执行命令测试 /usr/local/jdk1.7.0_79/bin/java -Djavax.net.debug=all SSLPoke confi.wangmanyuan.com 443 如下图展示 发现读取的并不是常规的cacerts这个文件而是jssecacerts文件 重新指定文件执行命令后解决 echo yes|keytool -importcert -file Digicert-OV-DV-root.cer -alias DigicertWangManYuan20240717 -keystore /usr/local/jdk1.7.0_79/jre/lib/security/jssecacerts -storepass changeit 3.5.5 查看导入G2根证书是否成功keytool -list -keystore /usr/local/jdk1.7.0_79/jre/lib/security/jssecacerts -storepass changeit|grep -i wangmanyuan 3.5.6开启搬砖模式由于非生产和生产环境数量非常多 那么接下来我就是去做这件事了 毕竟生产环境得重启 真是一个高危操作 好了，就写这么多吧，有遇到和我类似问题的童鞋可以进群交流～～～
今天出现了生产环境clickhouse 清理业务表数据磁盘并未下降的问题 搜索相关文档后得知system表内会继续各种操作日志 一、查询各个表占用空间信息select concat(database, '.', table) as table, formatReadableSize(sum(bytes)) as size, sum(rows) as rows, max(modification_time) as latest_modification, sum(bytes) as bytes_size, any(engine) as engine, formatReadableSize(sum(primary_key_bytes_in_memory)) as primary_keys_size from system.parts where active group by database, table order by bytes_size desc limit 10; 二、清理system 系统自带的日志表数据2.1 根据指定数据库清理SQLalter table system.query_thread_log delete where current_database ='xxx' ; 2.2 根据指定数据库和时间清理SQLalter table system.query_thread_log delete where current_database ='xxx' and event_date='2023-02-05';
A.新建分支goods_cate新建分支goods_cate并推送到码云git checkout -b goods_categit push -u origin goods_cateB.创建子级路由创建categories子级路由组件并设置路由规则import Cate from './components/goods/Cate.vue' path: '/home', component: Home, redirect: '/welcome', children: [ { path: "/welcome", component: Welcome }, { path: "/users", component: Users }, { path: "/rights", component: Rights }, { path: "/roles", component: Roles }, { path: "/categories", component: Cate } ] C.添加组件基本布局在Cate.vue组件中添加面包屑导航以及卡片视图中的添加分类按钮 商品分类 <el-breadcrumb separator="/"> <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item> <el-breadcrumb-item>商品管理</el-breadcrumb-item> <el-breadcrumb-item>商品分类</el-breadcrumb-item> </el-breadcrumb> <el-card> <el-row> <el-col> <el-button type="primary">添加分类</el-button> </el-col> </el-row> </el-card> D.请求分类数据请求分类数据并将数据保存在data中 E.使用插件展示数据使用第三方插件vue-table-with-tree-grid展示分类数据1).在vue 控制台中点击依赖->安装依赖->运行依赖->输入vue-table-with-tree-gird->点击安装2).打开main.js，导入vue-table-with-tree-gridimport TreeTable from 'vue-table-with-tree-grid'.....Vue.config.productionTip = false//全局注册组件 Vue.component('tree-table', TreeTable) 3).使用组件展示分类数据 <tree-table :data="cateList" :columns="columns" :selection-type="false" :expand-type="false" show-index index-text="#" border :show-row-hover="false"> </tree-table> 在数据中添加columns: columns: [ {label:'分类名称',prop:'cat_name'} ] F.自定义数据列使用vue-table-with-tree-grid定义模板列并添加自定义列//先在columns中添加一个列 columns: [ {label:'分类名称',prop:'cat_name'}, //type:'template'(将该列设置为模板列)，template:'isok'(设置该列模板的名称为isok) {label:'是否有效',prop:'',type:'template',template:'isok'}, {label:'排序',prop:'',type:'template',template:'order'}, {label:'操作',prop:'',type:'template',template:'opt'} ] <el-tag size="mini" v-if="scope.row.cat_level===0">一级</el-tag> <el-tag size="mini" type="success" v-else-if="scope.row.cat_level===1">二级</el-tag> <el-tag size="mini" type="warning" v-else>三级</el-tag> <el-button size="mini" type="primary" icon="el-icon-edit">编辑</el-button> <el-button size="mini" type="danger" icon="el-icon-delete">删除</el-button> G.完成分页功能 <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="queryInfo.pagenum" :page-sizes="[3, 5, 10, 15]" :page-size="queryInfo.pagesize" layout="total, sizes, prev, pager, next, jumper" :total="total"> </el-pagination> //添加对应的事件函数 methods:{ ....... handleSizeChange(newSize){ //当pagesize发生改变时触发 this.queryInfo.pagesize = newSize; this.getCateList(); }, handleCurrentChange(newPage){ //当pagenum发生改变时触发 this.queryInfo.pagenum = newPage; this.getCateList(); } } H.完成添加分类...... <el-row> <el-col> <el-button type="primary" @click="showAddCateDialog">添加分类</el-button> </el-col> </el-row> ...... <el-dialog title="添加分类" :visible.sync="addCateDialogVisible" width="50%" @close="addCateDialogClosed"> <el-form :model="addCateForm" :rules="addCateFormRules" ref="addCateFormRuleForm" label-width="100px"> <el-form-item label="分类名称" prop="cat_name"> <el-input v-model="addCateForm.cat_name"></el-input> </el-form-item> <el-form-item label="父级分类" prop="cat_pid"> </el-form-item> </el-form> <el-button @click="addCateDialogVisible = false">取 消</el-button> <el-button type="primary" @click="addCate">确 定</el-button> </el-dialog> //用来显示或隐藏添加分类对话框 addCateDialogVisible: false, //添加分类的表单数据对象 addCateForm:{ //分类名称 cat_name:'', //添加分类的父级id，0则表示父级为0.添加一级分类 cat_pid:0, //添加分类的等级，0则表示添加一级分类 cat_level:0 }, //添加分类校验规则 addCateFormRules:{ //验证规则 cat_name:[ {required:true , message:'请输入分类名称',trigger:'blur'} ] }, //保存1,2级父级分类的列表 parentCateList:[] ....... showAddCateDialog() { //调用getParentCateList获取分类列表 this.getParentCateList() //显示添加分类对话框 this.addCateDialogVisible = true }, async getParentCateList(){ //获取父级分类数据列表 const { data: res } = await this.$http.get('categories', { params: {type:2} }) if (res.meta.status !== 200) { return this.$message.error('获取商品分类列表数据失败') } this.parentCateList = res.data } 添加级联菜单显示父级分类先导入Cascader组件，并注册然后添加使用级联菜单组件：<el-form-item label="父级分类" prop="cat_pid"> <el-cascader expandTrigger='hover' v-model="selectedKeys" :options="parentCateList" :props="cascaderProps" @change="parentCateChange" clearable change-on-select></el-cascader> </el-form-item> 添加数据 //配置级联菜单中数据如何展示 cascaderProps:{ value:'cat_id', label:'cat_name', children:'children', expandTrigger:'hover' }, //绑定用户选择的分类值 selectedKeys:[] ..... methods:{ ..... parentCateChange(){ //级联菜单中选择项发生变化时触发 console.log(this.selectedKeys) //如果用户选择了父级分类 if(this.selectedKeys.length > 0){ //则将数组中的最后一项设置为父级分类 this.addCateForm.cat_pid = this.selectedKeys[this.selectedKeys.length - 1] //level也要跟着发生变化 this.addCateForm.cat_level = this.selectedKeys.length return }else{ this.addCateForm.cat_pid = 0 this.addCateForm.cat_level = 0 return } }, addCateDialogClosed(){ //当关闭添加分类对话框时，重置表单 this.$refs.addCateFormRef.resetFields() this.selectedKeys = []; this.addCateForm.cat_pid = 0 this.addCateForm.cat_level = 0 }, addCate() { //点击确定，完成添加分类 console.log(this.addCateForm) this.$refs.addCateFormRef.validate(async valid => { if (!valid) return //发送请求完成添加分类 const { data: res } = await this.$http.post( 'categories', this.addCateForm ) if (res.meta.status !== 201) { return this.$message.error('添加分类失败') } this.$message.success('添加分类成功') this.getCateList() this.addCateDialogVisible = false }) } } I.推送代码制作完添加分类之后，将代码提交到仓库，推送到码云,将goods_cate分支合并到mastergit add .git commit -m '完成商品分类'git pushgit checkout mastergit merge goods_cate2.参数管理只允许给三级分类内容设置参数，参数分为动态参数和静态参数属性A.添加子级组件添加Params.vue子组件，并在router.js中引入该组件并设置路由规则import Params from './components/goods/Params.vue' ...... path: '/home', component: Home, redirect: '/welcome', children: [ { path: "/welcome", component: Welcome }, { path: "/users", component: Users }, { path: "/rights", component: Rights }, { path: "/roles", component: Roles }, { path: "/categories", component: Cate }, { path: "/params", component: Params } ] B.完成组件基本布局完成Params.vue组件的基本布局其中警告提示信息使用了el-alert，在element.js引入该组件并注册 分类参数 <el-breadcrumb separator="/"> <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item> <el-breadcrumb-item>商品管理</el-breadcrumb-item> <el-breadcrumb-item>分类参数</el-breadcrumb-item> </el-breadcrumb> <el-card> <el-alert title="注意：只允许为第三级分类设置相关参数" type="warning" :closable="false" show-icon> </el-alert> <el-row class="cat_opt"> <el-col> 选择商品分类： </el-col> <el-col></el-col> </el-row> </el-card> C.完成级联选择框完成商品分类级联选择框 <el-row class="cat_opt"> <el-col> 选择商品分类： <el-cascader expandTrigger='hover' v-model="selectedCateKeys" :options="cateList" :props="cateProps" @change="handleChange" clearable></el-cascader> </el-col> <el-col></el-col> </el-row> ...... D.展示参数展示动态参数数据以及静态属性数据 <el-tabs v-model="activeName" @tab-click="handleTabClick"> <el-tab-pane label="动态参数" name="many"> <el-button size="mini" type="primary" :disabled="isButtonDisabled">添加参数</el-button> <el-table :data="manyTableData" border stripe> <el-table-column type="expand"></el-table-column> <el-table-column type="index"></el-table-column> <el-table-column label="参数名称" prop="attr_name"></el-table-column> <el-table-column label="操作"> <el-button size="mini" type="primary" icon="el-icon-edit">编辑</el-button> <el-button size="mini" type="danger" icon="el-icon-delete">删除</el-button> </el-table-column> </el-table> </el-tab-pane> <el-tab-pane label="静态属性" name="only"> <el-button size="mini" type="primary" :disabled="isButtonDisabled">添加属性</el-button> <el-table :data="onlyTableData" border stripe> <el-table-column type="expand"></el-table-column> <el-table-column type="index"></el-table-column> <el-table-column label="属性名称" prop="attr_name"></el-table-column> <el-table-column label="操作"> <el-button size="mini" type="primary" icon="el-icon-edit">编辑</el-button> <el-button size="mini" type="danger" icon="el-icon-delete">删除</el-button> </el-table-column> </el-table> </el-tab-pane> </el-tabs> export default { data() { return { ...... //tab页签激活显示的页签项 activeName: 'many', //用来保存动态参数数据 manyTableData: [], //用来保存静态属性数据 onlyTableData: [] } methods: { ....... async handleChange() { //当用户在级联菜单中选择内容改变时触发 console.log(this.selectedCateKeys) //发送请求，根据用户选择的三级分类和面板获取参数数据 const { data: res } = await this.$http.get( `categories/${this.cateId}/attributes`, { params: { sel: this.activeName } } ) if (res.meta.status !== 200) { return this.$message.error('获取参数列表数据失败') } console.log(res.data) if (this.activeName === 'many') { //获取的是动态参数 this.manyTableData = res.data } else if (this.activeName === 'only') { //获取的是静态属性 this.onlyTableData = res.data } }, handleTabClick() { console.log(this.activeName) this.handleChange() } }, computed: { //添加计算属性用来获取按钮禁用与否 isButtonDisabled() { return this.selectedCateKeys.length !== 3 }, //获取选中的三级分类id cateId() { if (this.selectedCateKeys.length === 3) { return this.selectedCateKeys[this.selectedCateKeys.length - 1] } return null } } E.添加参数完成添加参数或属性 <el-dialog :title="'添加'+titleText" :visible.sync="addDialogVisible" width="50%" @close="addDialogClosed"> <el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="100px"> <el-form-item :label="titleText" prop="attr_name"> <el-input v-model="addForm.attr_name"></el-input> </el-form-item> </el-form> <el-button @click="addDialogVisible = false">取 消</el-button> <el-button type="primary" @click="addParams">确 定</el-button> </el-dialog> export default { data() { return { ....... //控制添加参数.属性对话框的显示或隐藏 addDialogVisible: false, //添加参数的表单数据对象 addForm: { attr_name: '' }, //添加表单验证规则 addFormRules: { attr_name: [{ required: true, message: '请输入名称', trigger: 'blur' }] } } },methods: { ....... addParams() { //当用户点击对话框中的确定时，校验表单 this.$refs.addFormRef.validate(async valid => { //校验不通过，return if (!valid) return //校验通过，发送请求完成添加参数或者属性 const { data: res } = this.$http.post(`categories/${this.cateId}/attributes`, { attr_name: this.addForm.attr_name, attr_sel: this.activeName, attr_vals: "a,b,c" } ) console.log(res) if (res.meta.status !== 201) { return this.$message.error('添加' + this.titleText + '数据失败') } this.$message.success('添加' + this.titleText + '数据成功') this.addDialogVisible = false this.getCateList() }) } } F.编辑参数完成编辑参数或属性 <el-dialog :title="'修改'+titleText" :visible.sync="editDialogVisible" width="50%" @close="editDialogClosed"> <el-form :model="editForm" :rules="editFormRules" ref="editFormRef" label-width="100px"> <el-form-item :label="titleText" prop="attr_name"> <el-input v-model="editForm.attr_name"></el-input> </el-form-item> </el-form> <el-button @click="editDialogVisible = false">取 消</el-button> <el-button type="primary" @click="editParams">确 定</el-button> </el-dialog> export default { data() { return { ....... //控制修改参数.属性对话框的显示或隐藏 editDialogVisible:false, //修改参数.属性对话框中的表单 editForm:{ attr_name:'' }, //修改表单的验证规则 editFormRules:{ attr_name:[ { required: true, message: '请输入名称', trigger: 'blur' } ] } } },methods: { ....... async showEditDialog(attr_id){ //发起请求获取需要修改的那个参数数据 const {data:res} = await this.$http.get(`categories/${this.cateId}/attributes/${attr_id}`, {params:{ attr_sel:this.activeName }}) if (res.meta.status !== 200) { return this.$message.error('获取参数数据失败') } this.editForm = res.data; //显示修改参数.属性对话框 this.editDialogVisible = true; }, editDialogClosed(){ //当关闭修改参数.属性对话框时 this.$refs.editFormRef.resetFields() }, editParams(){ //验证表单 this.$refs.editFormRef.validate(async valid => { if(!valid) return; //发送请求完成修改 const {data:res} = await this.$http.put(`categories/${this.cateId}/attributes/${this.editForm.attr_id}`, {attr_name:this.editForm.attr_name,attr_sel:this.activeName}) if (res.meta.status !== 200) { return this.$message.error('获取参数数据失败') } this.$message.success('修改' + this.titleText + '数据成功') this.editDialogVisible = false this.handleChange(); }) } } G.删除参数删除参数或属性给两个删除按钮添加事件 <el-button size="mini" type="danger" icon="el-icon-delete" @click="removeParams(scope.row.attr_id)">删除</el-button> <el-button size="mini" type="danger" icon="el-icon-delete" @click="removeParams(scope.row.attr_id)">删除</el-button> 添加对应的事件处理函数 async removeParams(attr_id){ //根据id删除对应的参数或属性 //弹窗提示用户是否要删除 const confirmResult = await this.$confirm( '请问是否要删除该'+this.titleText, '删除提示', { confirmButtonText: '确认删除', cancelButtonText: '取消', type: 'warning' } ).catch(err => err) //如果用户点击确认，则confirmResult 为'confirm' //如果用户点击取消, 则confirmResult获取的就是catch的错误消息'cancel' if (confirmResult != 'confirm') { return this.$message.info('已经取消删除') } //没有取消就是要删除，发送请求完成删除 const {data:res} = await this.$http.delete(`categories/${this.cateId}/attributes/${attr_id}`) if (res.meta.status !== 200) { return this.$message.error('删除参数数据失败') } this.$message.success('删除' + this.titleText + '数据成功') this.handleChange() }
Ansible之RolesRoles介绍ansible自1.2版本引入的新特性，用于层次性、结构化地组织playbook。roles能够根据层次型结构自动装载变量文件、tasks以及handlers等。要使用roles只需要在playbook中使用include指令引入即可。简单来讲，roles就是通过分别将变量、文件、任务、模板及处理器放置于单独的目录中，并可以便捷的include它们的一种机制。角色一般用于基于主机构建服务的场景中，但也可以是用于构建守护进程等场景中。主要使用场景代码复用度较高的情况下。Roles目录结构各目录含义解释roles: <--所有的角色必须放在roles目录下，这个目录可以自定义位置，默认的位置在/etc/ansible/roles project: <---具体的角色项目名称，比如nginx、tomcat、php files： <--用来存放由copy模块或script模块调用的文件。 templates： <--用来存放jinjia2模板，template模块会自动在此目录中寻找jinjia2模板文件。 tasks： <--此目录应当包含一个main.yml文件，用于定义此角色的任务列表，此文件可以使用include包含其它的位于此目录的task文件。 main.yml handlers： <--此目录应当包含一个main.yml文件，用于定义此角色中触发条件时执行的动作。 main.yml vars： <--此目录应当包含一个main.yml文件，用于定义此角色用到的变量。 main.yml defaults： <--此目录应当包含一个main.yml文件，用于为当前角色设定默认变量。 main.yml meta： <--此目录应当包含一个main.yml文件，用于定义此角色的特殊设定及其依赖关系。 main.yml Roles示例通过ansible roles安装配置httpd服务，此处的roles使用默认的路径/etc/ansible/roles1）创建目录[root@ansible ~]# cd /etc/ansible/roles/ # 创建需要用到的目录 [root@ansible roles]# mkdir -p httpd/{handlers,tasks,templates,vars} [root@ansible roles]# cd httpd/ [root@ansible httpd]# tree . . ├── handlers ├── tasks ├── templates └── vars 4 directories, 0 file 2）变量文件准备vars/main.yml[root@ansible httpd]# vim vars/main.yml PORT: 8088 #指定httpd监听的端口 USERNAME: www #指定httpd运行用户 GROUPNAME: www #指定httpd运行组 3）配置文件模板准备templates/httpd.conf.j2# copy一个本地的配置文件放在templates/下并已j2为后缀 [root@ansible httpd]# cp /etc/httpd/conf/httpd.conf templates/httpd.conf.j2 # 进行一些修改，调用上面定义的变量 [root@ansible httpd]# vim templates/httpd.conf.j2 Listen {{ PORT }} User {{ USERNAME }} Group {{ GROUPNAME }} 4）任务剧本编写，创建用户、创建组、安装软件、配置、启动等# 创建组的task [root@ansible httpd]# vim tasks/group.yml - name: Create a Startup Group group: name=www gid=60 system=yes # 创建用户的task [root@ansible httpd]# vim tasks/user.yml - name: Create Startup Users user: name=www uid=60 system=yes shell=/sbin/nologin # 安装软件的task [root@ansible httpd]# vim tasks/install.yml - name: Install Package Httpd yum: name=httpd state=installed # 配置软件的task [root@ansible httpd]# vim tasks/config.yml - name: Copy Httpd Template File template: src=httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf notify: Restart Httpd # 启动软件的task [root@ansible httpd]# vim tasks/start.yml - name: Start Httpd Service service: name=httpd state=started enabled=yes # 编写main.yml，将上面的这些task引入进来 [root@ansible httpd]# vim tasks/main.yml - include: group.yml - include: user.yml - include: install.yml - include: config.yml - include: start.ym 5）编写重启httpd的handlers，handlers/main.yml[root@ansible httpd]# vim handlers/main.yml # 这里的名字需要和task中的notify保持一致 - name: Restart Httpd service: name=httpd state=restarted 6）编写主的httpd_roles.yml文件调用httpd角色[root@ansible httpd]# cd .. [root@ansible roles]# vim httpd_roles.yml --- - hosts: all remote_user: root roles: - role: httpd #指定角色名称 7）整体的一个目录结构查看[root@ansible roles]# tree . . ├── httpd │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ ├── config.yml │ │ ├── group.yml │ │ ├── install.yml │ │ ├── main.yml │ │ ├── start.yml │ │ └── user.yml │ ├── templates │ │ └── httpd.conf.j2 │ └── vars │ └── main.yml └── httpd_roles.yml 5 directories, 10 files 8）测试playbook语法是否正确[root@ansible roles]# ansible-playbook -C httpd_roles.yml PLAY [all] ************************************************************************************************** TASK [Gathering Facts] ************************************************************************************** ok: [192.168.1.33] ok: [192.168.1.32] ok: [192.168.1.31] ok: [192.168.1.36] TASK [httpd : Create a Startup Group] *********************************************************************** changed: [192.168.1.31] changed: [192.168.1.33] changed: [192.168.1.36] changed: [192.168.1.32] TASK [httpd : Create Startup Users] ************************************************************************* changed: [192.168.1.33] changed: [192.168.1.32] changed: [192.168.1.31] changed: [192.168.1.36] TASK [httpd : Install Package Httpd] ************************************************************************ changed: [192.168.1.33] changed: [192.168.1.32] changed: [192.168.1.31] changed: [192.168.1.36] TASK [httpd : Copy Httpd Template File] ********************************************************************* changed: [192.168.1.33] changed: [192.168.1.36] changed: [192.168.1.32] changed: [192.168.1.31] TASK [httpd : Start Httpd Service] ************************************************************************** changed: [192.168.1.36] changed: [192.168.1.31] changed: [192.168.1.32] changed: [192.168.1.33] RUNNING HANDLER [httpd : Restart Httpd] ********************************************************************* changed: [192.168.1.36] changed: [192.168.1.33] changed: [192.168.1.32] changed: [192.168.1.31] PLAY RECAP ************************************************************************************************** 192.168.1.31 : ok=7 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.1.32 : ok=7 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.1.33 : ok=7 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.1.36 : ok=7 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 9）上面的测试没有问题，正式执行playbook[root@ansible roles]# ansible-playbook -C httpd_roles.yml PLAY [all] ************************************************************************************************** TASK [Gathering Facts] ************************************************************************************** ok: [192.168.1.33] ok: [192.168.1.32] ok: [192.168.1.31] ok: [192.168.1.36] TASK [httpd : Create a Startup Group] *********************************************************************** changed: [192.168.1.31] changed: [192.168.1.33] changed: [192.168.1.36] changed: [192.168.1.32] TASK [httpd : Create Startup Users] ************************************************************************* changed: [192.168.1.33] changed: [192.168.1.32] changed: [192.168.1.31] changed: [192.168.1.36] TASK [httpd : Install Package Httpd] ************************************************************************ changed: [192.168.1.33] changed: [192.168.1.32] changed: [192.168.1.31] changed: [192.168.1.36] TASK [httpd : Copy Httpd Template File] ********************************************************************* changed: [192.168.1.33] changed: [192.168.1.36] changed: [192.168.1.32] changed: [192.168.1.31] TASK [httpd : Start Httpd Service] ************************************************************************** changed: [192.168.1.36] changed: [192.168.1.31] changed: [192.168.1.32] changed: [192.168.1.33] RUNNING HANDLER [httpd : Restart Httpd] ********************************************************************* changed: [192.168.1.36] changed: [192.168.1.33] changed: [192.168.1.32] changed: [192.168.1.31] PLAY RECAP ************************************************************************************************** 192.168.1.31 : ok=7 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.1.32 : ok=7 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.1.33 : ok=7 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.1.36 : ok=7 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [root@ansible roles]# ansible-playbook httpd_roles.yml PLAY [all] ************************************************************************************************** TASK [Gathering Facts] ************************************************************************************** ok: [192.168.1.32] ok: [192.168.1.33] ok: [192.168.1.31] ok: [192.168.1.36] TASK [httpd : Create a Startup Group] *********************************************************************** changed: [192.168.1.32] changed: [192.168.1.31] changed: [192.168.1.33] changed: [192.168.1.36] TASK [httpd : Create Startup Users] ************************************************************************* changed: [192.168.1.31] changed: [192.168.1.33] changed: [192.168.1.32] changed: [192.168.1.36] TASK [httpd : Install Package Httpd] ************************************************************************ changed: [192.168.1.31] changed: [192.168.1.33] changed: [192.168.1.32] changed: [192.168.1.36] TASK [httpd : Copy Httpd Template File] ********************************************************************* changed: [192.168.1.33] changed: [192.168.1.32] changed: [192.168.1.31] changed: [192.168.1.36] TASK [httpd : Start Httpd Service] ************************************************************************** fatal: [192.168.1.36]: FAILED! => {"changed": false, "msg": "httpd: Syntax error on line 56 of /etc/httpd/conf/httpd.conf: Include directory '/etc/httpd/conf.modules.d' not found\n"} changed: [192.168.1.33] changed: [192.168.1.32] changed: [192.168.1.31] RUNNING HANDLER [httpd : Restart Httpd] ********************************************************************* changed: [192.168.1.33] changed: [192.168.1.32] changed: [192.168.1.31] PLAY RECAP ************************************************************************************************** 192.168.1.31 : ok=7 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.1.32 : ok=7 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.1.33 : ok=7 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.1.36 : ok=5 changed=4 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 这里有看到报错，其原因是因为192.168.1.36这台机器是centos6系统，别的都是centos7系统，两个系统安装的httpd默认版本是不一样的，所以报错。如果需要优化。参考这里ansible roles总结1、编写任务(task)的时候，里面不需要写需要执行的主机，单纯的写某个任务是干什么的即可，装软件的就是装软件的，启动的就是启动的。单独做某一件事即可，最后通过main.yml将这些单独的任务安装执行顺序include进来即可，这样方便维护且一目了然。2、定义变量时候直接安装k:v格式将变量写在vars/main.yml文件即可，然后task或者template直接调用即可，会自动去vars/main.yml文件里面去找。3、定义handlers时候，直接在handlers/main.yml文件中写需要做什么事情即可，多可的话可以全部写在该文件里面，也可以像task那样分开来写，通过include引入一样的可以。在task调用notify时直接写与handlers名字对应即可(二者必须高度一直)。4、模板文件一样放在templates目录下即可，task调用的时后直接写文件名字即可，会自动去到templates里面找。注意：如果是一个角色调用另外一个角色的单个task时后，那么task中如果有些模板或者文件，就得写绝对路径了。出处：https://www.cnblogs.com/yanjieli/p/10971862.html版权：本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
Ansible Ad-hoc模式常用模块ansible-doc 常用命令# ansible-doc -h Usage: ansible-doc [-l|-F|-s] [options] [-t <plugin type> ] [plugin] -j 以json格式显示所有模块信息 -l 列出所有的模块 -s 查看模块常用参数 # 直接跟模块名，显示模块所有信息 [root@ansible ~]# ansible-doc -j [root@ansible ~]# ansible-doc -l [root@ansible ~]# ansible-doc -l |wc -l #统计所有模块个数，ansible2.8共计2834个模块 2834 命令相关的模块commandansible默认的模块,执行命令，注意：shell中的"<", ">", "|", ";", "&","$"等特殊字符不能在command模块中使用，如果需要使用，则用shell模块# 查看模块参数 [root@ansible ~]# ansible-doc -s command # 在192.168.1.31服务器上面执行ls命令，默认是在当前用户的家目录/root [root@ansible ~]# ansible 192.168.1.31 -a 'ls' # chdir 先切换工作目录，再执行后面的命令，一般情况下在编译时候使用 [root@ansible ~]# ansible 192.168.1.31 -a 'chdir=/tmp pwd' 192.168.1.31 | CHANGED | rc=0 >> /tmp # creates 如果creates的文件存在，则执行后面的操作 [root@ansible ~]# ansible 192.168.1.31 -a 'creates=/tmp ls /etc/passwd' #tmp目录存在，则不执行后面的ls命令 192.168.1.31 | SUCCESS | rc=0 >> skipped, since /tmp exists [root@ansible ~]# ansible 192.168.1.31 -a 'creates=/tmp11 ls /etc/passwd' # tmp11文件不存在，则执行后面的ls命令 192.168.1.31 | CHANGED | rc=0 >> /etc/passwd # removes 和creates相反，如果removes的文件存在，才执行后面的操作 [root@ansible ~]# ansible 192.168.1.31 -a 'removes=/tmp ls /etc/passwd' #tmp文件存在，则执行了后面的ls命令 192.168.1.31 | CHANGED | rc=0 >> /etc/passwd [root@ansible ~]# ansible 192.168.1.31 -a 'removes=/tmp11 ls /etc/passwd' #tmp11文件不存在，则没有执行后面的ls命令 192.168.1.31 | SUCCESS | rc=0 >> skipped, since /tmp11 does not exist shell专门用来执行shell命令的模块，和command模块一样，参数基本一样，都有chdir,creates,removes等参数# 查看模块参数 [root@ansible ~]# ansible-doc -s shell [root@ansible ~]# ansible 192.168.1.31 -m shell -a 'mkdir /tmp/test' [root@ansible ~]# ansible 192.168.1.31 -m shell -a 'ls /tmp' #执行下面这条命令，每次执行都会更新文件的时间戳 [root@ansible ~]# ansible 192.168.1.31 -m shell -a 'cd /tmp/test && touch 1.txt && ls' 192.168.1.31 | CHANGED | rc=0 >> 1.txt # 由于有时候不想更新文件的创建时间戳，则如果存在就不执行creates [root@ansible ~]# ansible 192.168.1.31 -m shell -a 'creates=/tmp/test/1.txt cd /tmp/test && touch 1.txt && ls' 192.168.1.31 | SUCCESS | rc=0 >> skipped, since /tmp/test/1.txt exists script用于在被管理机器上面执行shell脚本的模块，脚本无需在被管理机器上面存在# 查看模块参数 [root@ansible ~]# ansible-doc -s script # 编写shell脚本 [root@ansible ~]# vim ansible_test.sh #!/bin/bash echo `hostname` # 在所有被管理机器上执行该脚本 [root@ansible ~]# ansible all -m script -a '/root/ansible_test.sh' 192.168.1.32 | CHANGED => { "changed": true, "rc": 0, "stderr": "Shared connection to 192.168.1.32 closed.\r\n", "stderr_lines": [ "Shared connection to 192.168.1.32 closed." ], "stdout": "linux.node02.com\r\n", "stdout_lines": [ "linux.node02.com" ] } ...... 文件相关的模块file用于对文件的处理，创建，删除，权限控制等# 查看模块参数 [root@ansible ~]# ansible-doc -s file path #要管理的文件路径 recurse #递归 state： directory #创建目录，如果目标不存在则创建目录及其子目录 touch #创建文件，如果文件存在，则修改文件 属性 absent #删除文件或目录 mode #设置文件或目录权限 owner #设置文件或目录属主信息 group #设置文件或目录属组信息 link #创建软连接，需要和src配合使用 hard #创建硬连接，需要和src配合使用 # 创建目录 [root@ansible ~]# ansible 192.168.1.31 -m file -a 'path=/tmp/test1 state=directory' # 创建文件 [root@ansible ~]# ansible 192.168.1.31 -m file -a 'path=/tmp/test2 state=touch' # 建立软链接（src表示源文件，path表示目标文件） [root@ansible ~]# ansible 192.168.1.31 -m file -a 'src=/tmp/test1 path=/tmp/test3 state=link' # 删除文件 [root@ansible ~]# ansible 192.168.1.31 -m file -a 'path=/tmp/test2 state=absent' # 创建文件时同时设置权限等信息 [root@ansible ~]# ansible 192.168.1.31 -m file -a 'path=/tmp/test4 state=directory mode=775 owner=root group=root' copy用于管理端复制文件到远程主机，并可以设置权限，属组，属主等# 查看模块参数 [root@ansible ~]# ansible-doc -s copy src #需要copy的文件的源路径 dest #需要copy的文件的目标路径 backup #对copy的文件进行备份 content #直接在远程主机被管理文件中添加内容，会覆盖原文件内容 mode #对copy到远端的文件设置权限 owner #对copy到远端的文件设置属主 group #对copy到远端文件设置属组 # 复制文件到远程主机并改名 [root@ansible ~]# ansible 192.168.1.31 -m copy -a 'dest=/tmp/a.sh src=/root/ansible_test.sh' # 复制文件到远程主机，并备份远程文件,安装时间信息备份文件（当更新文件内容后，重新copy时用到） [root@ansible ~]# ansible 192.168.1.31 -m copy -a 'dest=/tmp/a.sh src=/root/ansible_test.sh backup=yes' # 直接在远程主机a.sh中添加内容 [root@ansible ~]# ansible 192.168.1.31 -m copy -a 'dest=/tmp/a.sh content="#!/bin/bash\n echo `uptime`"' # 复制文件到远程主机，并设置权限及属主与属组 [root@ansible ~]# ansible 192.168.1.31 -m copy -a 'dest=/tmp/passwd src=/etc/passwd mode=700 owner=root group=root' fetch用于从被管理机器上面拉取文件，拉取下来的内容会保留目录结构，一般情况用在收集被管理机器的日志文件等# 查看模块参数 [root@ansible ~]# ansible-doc -s fetch src #指定需要从远端机器拉取的文件路径 dest #指定从远端机器拉取下来的文件存放路径 # 从被管理机器上拉取cron日志文件，默认会已管理节点地址创建一个目录，并存放在内 [root@ansible ~]# ansible 192.168.1.31 -m fetch -a 'dest=/tmp src=/var/log/cron' [root@ansible ~]# tree /tmp/192.168.1.31/ /tmp/192.168.1.31/ └── var └── log └── cron 2 directories, 1 file 用户相关的模块user用于对系统用户的管理，用户的创建、删除、家目录、属组等设置# 查看模块参数 [root@ansible ~]# ansible-doc -s user name #指定用户的名字 home #指定用户的家目录 uid #指定用户的uid group #指定用户的用户组 groups #指定用户的附加组 password #指定用户的密码 shell #指定用户的登录shell create_home #是否创建用户家目录，默认是yes remove #删除用户时，指定是否删除家目录 state： absent #删除用户 # 创建用户名指定家目录，指定uid及组 [root@ansible ~]# ansible 192.168.1.31 -m user -a 'name=mysql home=/opt/mysql uid=1002 group=root' [root@ansible ~]# ansible 192.168.1.31 -m shell -a 'id mysql && ls -l /opt' 192.168.1.31 | CHANGED | rc=0 >> uid=1002(mysql) gid=0(root) 组=0(root) 总用量 0 drwx------ 3 mysql root 78 5月 27 18:13 mysql # 创建用户，不创建家目录，并且不能登录 [root@ansible ~]# ansible 192.168.1.31 -m user -a 'name=apache shell=/bin/nologin uid=1003 create_home=no' [root@ansible ~]# ansible 192.168.1.31 -m shell -a 'id apache && tail -1 /etc/passwd' 192.168.1.31 | CHANGED | rc=0 >> uid=1003(apache) gid=1003(apache) 组=1003(apache) apache:x:1003:1003::/home/apache:/bin/nologin # 删除用户 [root@ansible ~]# ansible 192.168.1.31 -m user -a 'name=apache state=absent' # 删除用户并删除家目录 [root@ansible ~]# ansible 192.168.1.31 -m user -a 'name=mysql state=absent remove=yes' group用于创建组，当创建用户时如果需要指定组，组不存在的话就可以通过group先创建组# 查看模块参数 [root@ansible ~]# ansible-doc -s group name #指定组的名字 gid #指定组的gid state： absent #删除组 present #创建组（默认的状态） # 创建组 [root@ansible ~]# ansible 192.168.1.31 -m group -a 'name=www' # 创建组并指定gid [root@ansible ~]# ansible 192.168.1.31 -m group -a 'name=www1 gid=1005' # 删除组 [root@ansible ~]# ansible 192.168.1.31 -m group -a 'name=www1 state=absent' 软件包相关的模块yum用于对软件包的管理，下载、安装、卸载、升级等操作# 查看模块参数 [root@ansible ~]# ansible-doc -s yum name #指定要操作的软件包名字 download_dir #指定下载软件包的存放路径，需要配合download_only一起使用 download_only #只下载软件包，而不进行安装，和yum --downloadonly一样 list: installed #列出所有已安装的软件包 updates #列出所有可以更新的软件包 repos #列出所有的yum仓库 state: installed, present #安装软件包(两者任选其一都可以) removed, absent #卸载软件包 latest #安装最新软件包 # 列出所有已安装的软件包 [root@ansible ~]# ansible 192.168.1.31 -m yum -a 'list=installed' # 列出所有可更新的软件包 [root@ansible ~]# ansible 192.168.1.31 -m yum -a 'list=updates' #列出所有的yum仓库 [root@ansible ~]# ansible 192.168.1.31 -m yum -a 'list=repos' #只下载软件包并到指定目录下 [root@ansible ~]# ansible 192.168.1.31 -m yum -a 'name=httpd download_only=yes download_dir=/tmp' #安装软件包 [root@ansible ~]# ansible 192.168.1.31 -m yum -a 'name=httpd state=installed' #卸载软件包 [root@ansible ~]# ansible 192.168.1.31 -m yum -a 'name=httpd state=removed' #安装包组，类似yum groupinstall 'Development Tools' [root@ansible ~]# ansible 192.168.1.31 -m yum -a 'name="@Development Tools" state=installed' pip用于安装python中的包# 查看模块参数 [root@ansible ~]# ansible-doc -s pip # 使用pip时，需要保证被管理机器上有python-pip软件包 [root@ansible ~]# ansible 192.168.1.31 -m yum -a 'name=python-pip' # 安装pip包 [root@ansible ~]# ansible 192.168.1.31 -m pip -a 'name=flask' service服务模块，用于对服务进行管理，服务的启动、关闭、开机自启等# 查看模块参数 [root@ansible ~]# ansible-doc -s service name #指定需要管理的服务名 enabled #指定是否开机自启动 state: #指定服务状态 started #启动服务 stopped #停止服务 restarted #重启服务 reloaded #重载服务 # 启动服务，并设置开机自启动 [root@ansible ~]# ansible 192.168.1.31 -m service -a 'name=crond state=started enabled=yes' 计划任务相关的模块cron用于指定计划任务，和crontab -e一样# 查看模块参数 [root@ansible ~]# ansible-doc -s cron job #指定需要执行的任务 minute #分钟 hour #小时 day #天 month #月 weekday #周 name #对计划任务进行描述 state: absetn #删除计划任务 # 创建一个计划任务，并描述是干嘛用的 [root@ansible ~]# ansible 192.168.1.31 -m cron -a "name='这是一个测试的计划任务' minute=* hour=* day=* month=* weekday=* job='/bin/bash /root/test.sh'" [root@ansible ~]# ansible 192.168.1.31 -m shell -a 'crontab -l' 192.168.1.31 | CHANGED | rc=0 >> #Ansible: 这是一个测试的计划任务 * * * * * /bin/bash /root/test.sh # 创建一个没有带描述的计划任务 [root@ansible ~]# ansible 192.168.1.31 -m cron -a "job='/bin/sh /root/test.sh'" # 删除计划任务 [root@ansible ~]# ansible 192.168.1.31 -m cron -a "name='None' job='/bin/sh /root/test.sh' state=absent" 系统信息相关的模块setup用于获取系统信息的一个模块# 查看模块参数 [root@ansible ~]# ansible-doc -s setup # 查看系统所有信息 [root@ansible ~]# ansible 192.168.1.31 -m setup # filter 对系统信息进行过滤 [root@ansible ~]# ansible 192.168.1.31 -m setup -a 'filter=ansible_all_ipv4_addresses' # 常用的过滤选项 ansible_all_ipv4_addresses 所有的ipv4地址 ansible_all_ipv6_addresses 所有的ipv6地址 ansible_architecture 系统的架构 ansible_date_time 系统时间 ansible_default_ipv4 系统的默认ipv4地址 ansible_distribution 系统名称 ansible_distribution_file_variety 系统的家族 ansible_distribution_major_version 系统的版本 ansible_domain 系统所在的域 ansible_fqdn 系统的主机名 ansible_hostname 系统的主机名,简写 ansible_os_family 系统的家族 ansible_processor_cores cpu的核数 ansible_processor_count cpu的颗数 ansible_processor_vcpus cpu的个数 出处：https://www.cnblogs.com/yanjieli/p/10969143.html版权：本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
Ansible快速入门介绍Ansible是一款简单的运维自动化工具，只需要使用ssh协议连接就可以来进行系统管理，自动化执行命令，部署等任务。Ansible的特点1、ansible不需要单独安装客户端，也不需要启动任何服务2、ansible是python中的一套完整的自动化执行任务模块3、ansible playbook 采用yaml配置，对于自动化任务执行过一目了然Ansible组成结构 Ansible是Ansible的命令工具，核心执行工具；一次性或临时执行的操作都是通过该命令执行。 Ansible Playbook任务剧本（又称任务集），编排定义Ansible任务集的配置文件，由Ansible顺序依次执行，yaml格式。 InventoryAnsible管理主机的清单，默认是/etc/ansible/hosts文件。 ModulesAnsible执行命令的功能模块，Ansible2.3版本为止，共有1039个模块。还可以自定义模块。 Plugins插件，模块功能的补充，常有连接类型插件，循环插件，变量插件，过滤插件，插件功能用的较少。 API提供给第三方程序调用的应用程序编程接口。 环境准备 IP 系统 主机名 描述 192.168.1.30 CentOS7 ansible ansible管理节点 192.168.1.31 CentOS7 linux.node01.com 被管理节点1 192.168.1.32 CentOS7 linux.node02.com 被管理节点2 192.168.1.33 CentOS7 linux.node03.com 被管理节点3 192.168.1.36 CentOS6 linux.node06.com 被管理节点6 Ansible安装1）配置epel源[root@ansible ~]# wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo [root@ansible ~]# yum clean all [root@ansible ~]# yum makecache 2）安装ansible[root@ansible ~]# yum -y install ansible # 查看ansible版本 [root@ansible ~]# ansible --version ansible 2.8.0 config file = /etc/ansible/ansible.cfg configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules'] ansible python module location = /usr/lib/python2.7/site-packages/ansible executable location = /usr/bin/ansible python version = 2.7.5 (default, Aug 4 2017, 00:39:18) [GCC 4.8.5 20150623 (Red Hat 4.8.5-16)] Ansible Inventory文件Inventory文件通常用于定义要管理的主机的认证信息，例如ssh登录用户名、密码以及key相关信息。可以同时操作一个组的多台主机，组与主机组之间的关系都是通过inventory文件配置。配置文件路径为：/etc/ansible/hosts基于密码连接[root@ansible ~]# vim /etc/ansible/hosts # 方法一 主机+端口+密码 [webserver] 192.168.1.31 ansible_ssh_port=22 ansible_ssh_user=root ansible_ssh_pass="123456" 192.168.1.32 ansible_ssh_port=22 ansible_ssh_user=root ansible_ssh_pass="123456" 192.168.1.33 ansible_ssh_port=22 ansible_ssh_user=root ansible_ssh_pass="123456" 192.168.1.36 ansible_ssh_port=22 ansible_ssh_user=root ansible_ssh_pass="123456" # 方法二 主机+端口+密码 [webserver] 192.168.1.3[1:3] ansible_ssh_user=root ansible_ssh_pass="123456" # 方法二 主机+端口+密码 [webserver] 192.168.1.3[1:3] [webserver:vars] ansible_ssh_pass="123456" 基于秘钥连接基于秘钥连接需要先创建公钥和私钥，并发送给被管理机器1）生成公私钥[root@ansible ~]# ssh-keygen [root@ansible ~]# for i in {1,2,3,6}; do ssh-copy-id -i 192.168.1.3$i ; done 2）配置连接[root@ansible ~]# vim /etc/ansible/hosts # 方法一 主机+端口+密钥 [webserver] 192.168.1.31:22 192.168.1.32 192.168.1.33 192.168.1.36 # 方法一 别名主机+端口+密钥 [webserver] node1 ansible_ssh_host=192.168.1.31 ansible_ssh_port=22 node2 ansible_ssh_host=192.168.1.32 ansible_ssh_port=22 node3 ansible_ssh_host=192.168.1.33 ansible_ssh_port=22 node6 ansible_ssh_host=192.168.1.36 ansible_ssh_port=22 主机组的使用# 主机组变量名+主机+密码 [apache] 192.168.1.36 192.168.1.33 [apache.vars] ansible_ssh_pass='123456' # 主机组变量名+主机+密钥 [nginx] 192.168.1.3[1:2] # 定义多个组，把一个组当另外一个组的组员 [webserver:children] #webserver组包括两个子组：apache nginx apache nginx 临时指定inventory1）先编辑一个主机定义清单2）在执行命令是指定inventory[root@ansible ~]# ansible dockers -m ping -i /etc/dockers -o 192.168.1.33 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "ping": "pong"} 192.168.1.32 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "ping": "pong"} 192.168.1.31 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "ping": "pong"} Inventory内置参数Ansible Ad-Hocad-hoc —— 临时的，在ansible中是指需要快速执行，并且不需要保存的命令。说白了就是执行简单的命令——一条命令。对于复杂的命令则为playbook，类似于saltstack的state sls状态文件。ansible命令格式1）常用命令参数[root@ansible ~]# ansible -h Usage: ansible <host-pattern> [options] -a MODULE_ARGS #模块参数 -C, --check #检查语法 -f FORKS #并发 --list-hosts #列出主机列表 -m MODULE_NAME #模块名字 -o 使用精简的输出 2）示例[root@ansible ~]# ansible webserver -m shell -a 'uptime' -o 192.168.1.36 | CHANGED | rc=0 | (stdout) 13:46:14 up 1 day, 9:20, 4 users, load average: 0.00, 0.00, 0.00 192.168.1.33 | CHANGED | rc=0 | (stdout) 21:26:33 up 1 day, 8:51, 3 users, load average: 0.00, 0.01, 0.05 192.168.1.31 | CHANGED | rc=0 | (stdout) 21:26:33 up 1 day, 8:50, 3 users, load average: 0.00, 0.01, 0.05 192.168.1.32 | CHANGED | rc=0 | (stdout) 21:26:33 up 1 day, 8:59, 3 users, load average: 0.00, 0.01, 0.05 3）命令说明host-pattern格式目标target主机，主机组匹配方式主机的匹配# 一台目标主机 [root@ansible ~]# ansible 192.168.1.31 -m ping # 多台目标主机 [root@ansible ~]# ansible 192.168.1.31,192.168.1.32 -m ping # 所有目标主机 [root@ansible ~]# ansible all -m ping 组的匹配# 组的配置信息如下：这里定义了一个nginx组和一个apache组 [root@ansible ~]# ansible nginx --list hosts (2): 192.168.1.31 192.168.1.32 [root@ansible ~]# ansible apache --list hosts (3): 192.168.1.36 192.168.1.33 192.168.1.32 # 一个组的所有主机匹配 [root@ansible ~]# ansible apache -m ping # 匹配apache组中有，但是nginx组中没有的所有主机 [root@ansible ~]# ansible 'apache:!nginx' -m ping -o 192.168.1.36 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "ping": "pong"} 192.168.1.33 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "ping": "pong"} # 匹配apache组和nginx组中都有的机器（并集） [root@ansible ~]# ansible 'apache:&nginx' -m ping -o 192.168.1.32 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "ping": "pong"} # 匹配apache组nginx组两个组所有的机器（并集）；等于ansible apache,nginx -m ping [root@ansible ~]# ansible 'apache:nginx' -m ping -o 192.168.1.32 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "ping": "pong"} 192.168.1.31 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "ping": "pong"} 192.168.1.33 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "ping": "pong"} 192.168.1.36 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "ping": "pong"} 出处：https://www.cnblogs.com/yanjieli/p/10969089.html版权：本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
Tags: ansibleURL: https://www.cnblogs.com/yanjieli/p/10969299.htmlAnsible之PlaybookPlaybook介绍Playbook与ad-hoc相比,是一种完全不同的运用ansible的方式，类似与saltstack的state状态文件。ad-hoc无法持久使用，playbook可以持久使用。playbook是由一个或多个play组成的列表，play的主要功能在于将事先归并为一组的主机装扮成事先通过ansible中的task定义好的角色。从根本上来讲，所谓的task无非是调用ansible的一个module。将多个play组织在一个playbook中，即可以让它们联合起来按事先编排的机制完成某一任务Playbook核心元素 Hosts 执行的远程主机列表 Tasks 任务集 Varniables 内置变量或自定义变量在playbook中调用 Templates 模板，即使用模板语法的文件，比如配置文件等 Handlers 和notity结合使用，由特定条件触发的操作，满足条件方才执行，否则不执行 tags 标签，指定某条任务执行，用于选择运行playbook中的部分代码。 Playbook语法playbook使用yaml语法格式，后缀可以是yaml,也可以是yml。 在单一一个playbook文件中，可以连续三个连子号(--)区分多个play。还有选择性的连续三个点好(...)用来表示play的结尾，也可省略。 次行开始正常写playbook的内容，一般都会写上描述该playbook的功能。 使用#号注释代码。 缩进必须统一，不能空格和tab混用。 缩进的级别也必须是一致的，同样的缩进代表同样的级别，程序判别配置的级别是通过缩进结合换行实现的。 YAML文件内容和Linux系统大小写判断方式保持一致，是区分大小写的，k/v的值均需大小写敏感 k/v的值可同行写也可以换行写。同行使用:分隔。 v可以是个字符串，也可以是一个列表 一个完整的代码块功能需要最少元素包括 name: task 一个简单的示例# 创建playbook文件 [root@ansible ~]# cat playbook01.yml --- #固定格式 - hosts: 192.168.1.31 #定义需要执行主机 remote_user: root #远程用户 vars: #定义变量 http_port: 8088 #变量 tasks: #定义一个任务的开始 - name: create new file #定义任务的名称 file: name=/tmp/playtest.txt state=touch #调用模块，具体要做的事情 - name: create new user user: name=test02 system=yes shell=/sbin/nologin - name: install package yum: name=httpd - name: config httpd template: src=./httpd.conf dest=/etc/httpd/conf/httpd.conf notify: #定义执行一个动作(action)让handlers来引用执行，与handlers配合使用 - restart apache #notify要执行的动作，这里必须与handlers中的name定义内容一致 - name: copy index.html copy: src=/var/www/html/index.html dest=/var/www/html/index.html - name: start httpd service: name=httpd state=started handlers: #处理器：更加tasks中notify定义的action触发执行相应的处理动作 - name: restart apache #要与notify定义的内容相同 service: name=httpd state=restarted #触发要执行的动作 #测试页面准备 [root@ansible ~]# echo " playbook test file " >>/var/www/html/index.html #配置文件准备 [root@ansible ~]# cat httpd.conf |grep ^Listen Listen {{ http_port }} #执行playbook， 第一次执行可以加-C选项，检查写的playbook是否ok [root@ansible ~]# ansible-playbook playbook01.yml PLAY [192.168.1.31] ********************************************************************************************* TASK [Gathering Facts] ****************************************************************************************** ok: [192.168.1.31] TASK [create new file] ****************************************************************************************** changed: [192.168.1.31] TASK [create new user] ****************************************************************************************** changed: [192.168.1.31] TASK [install package] ****************************************************************************************** changed: [192.168.1.31] TASK [config httpd] ********************************************************************************************* changed: [192.168.1.31] TASK [copy index.html] ****************************************************************************************** changed: [192.168.1.31] TASK [start httpd] ********************************************************************************************** changed: [192.168.1.31] PLAY RECAP ****************************************************************************************************** 192.168.1.31 : ok=7 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 # 验证上面playbook执行的结果 [root@ansible ~]# ansible 192.168.1.31 -m shell -a 'ls /tmp/playtest.txt && id test02' 192.168.1.31 | CHANGED | rc=0 >> /tmp/playtest.txt uid=990(test02) gid=985(test02) 组=985(test02) [root@ansible ~]# curl 192.168.1.31:8088 playbook test file Playbook的运行方式通过ansible-playbook命令运行格式：ansible-playbook <filename.yml> ... [options][root@ansible PlayBook]# ansible-playbook -h #ansible-playbook常用选项： --check or -C #只检测可能会发生的改变，但不真正执行操作 --list-hosts #列出运行任务的主机 --list-tags #列出playbook文件中定义所有的tags --list-tasks #列出playbook文件中定义的所以任务集 --limit #主机列表 只针对主机列表中的某个主机或者某个组执行 -f #指定并发数，默认为5个 -t #指定tags运行，运行某一个或者多个tags。（前提playbook中有定义tags） -v #显示过程 -vv -vvv更详细 Playbook中元素属性主机与用户在一个playbook开始时，最先定义的是要操作的主机和用户--- - hosts: 192.168.1.31 remote_user: root 除了上面的定义外，还可以在某一个tasks中定义要执行该任务的远程用户tasks: - name: run df -h remote_user: test shell: name=df -h 还可以定义使用sudo授权用户执行该任务tasks任务列表每一个task必须有一个名称name,这样在运行playbook时，从其输出的任务执行信息中可以很清楚的辨别是属于哪一个task的，如果没有定义 name，action的值将会用作输出信息中标记特定的task。每一个playbook中可以包含一个或者多个tasks任务列表，每一个tasks完成具体的一件事，（任务模块）比如创建一个用户或者安装一个软件等，在hosts中定义的主机或者主机组都将会执行这个被定义的tasks。Handlers与Notify很多时候当我们某一个配置发生改变，我们需要重启服务，（比如httpd配置文件文件发生改变了）这时候就可以用到handlers和notify了；(当发生改动时)notify actions会在playbook的每一个task结束时被触发，而且即使有多个不同task通知改动的发生，notify actions知会被触发一次；比如多个resources指出因为一个配置文件被改动，所以apache需要重启，但是重新启动的操作知会被执行一次。[root@ansible ~]# cat httpd.yml #用于安装httpd并配置启动 --- - hosts: 192.168.1.31 remote_user: root tasks: - name: install httpd yum: name=httpd state=installed - name: config httpd template: src=/root/httpd.conf dest=/etc/httpd/conf/httpd.conf notify: - restart httpd - name: start httpd service: name=httpd state=started handlers: - name: restart httpd service: name=httpd state=restarted #这里只要对httpd.conf配置文件作出了修改，修改后需要重启生效，在tasks中定义了restart httpd这个action，然后在handlers中引用上面tasks中定义的notify。 Playbook中变量的使用环境说明：这里配置了两个组，一个apache组和一个nginx组[root@ansible PlayBook]# cat /etc/ansible/hosts [apache] 192.168.1.36 192.168.1.33 [nginx] 192.168.1.3[1:2] 命令行指定变量执行playbook时候通过参数-e传入变量，这样传入的变量在整个playbook中都可以被调用，属于全局变量[root@ansible PlayBook]# cat variables.yml --- - hosts: all remote_user: root tasks: - name: install pkg yum: name={{ pkg }} #执行playbook 指定pkg [root@ansible PlayBook]# ansible-playbook -e "pkg=httpd" variables.yml hosts文件中定义变量在/etc/ansible/hosts文件中定义变量，可以针对每个主机定义不同的变量，也可以定义一个组的变量，然后直接在playbook中直接调用。注意，组中定义的变量没有单个主机中的优先级高。# 编辑hosts文件定义变量 [root@ansible PlayBook]# vim /etc/ansible/hosts [apache] 192.168.1.36 webdir=/opt/test #定义单个主机的变量 192.168.1.33 [apache:vars] #定义整个组的统一变量 webdir=/web/test [nginx] 192.168.1.3[1:2] [nginx:vars] webdir=/opt/web # 编辑playbook文件 [root@ansible PlayBook]# cat variables.yml --- - hosts: all remote_user: root tasks: - name: create webdir file: name={{ webdir }} state=directory #引用变量 # 执行playbook [root@ansible PlayBook]# ansible-playbook variables.yml playbook文件中定义变量编写playbook时，直接在里面定义变量，然后直接引用，可以定义多个变量；注意：如果在执行playbook时，又通过-e参数指定变量的值，那么会以-e参数指定的为准。# 编辑playbook [root@ansible PlayBook]# cat variables.yml --- - hosts: all remote_user: root vars: #定义变量 pkg: nginx #变量1 dir: /tmp/test1 #变量2 tasks: - name: install pkg yum: name={{ pkg }} state=installed #引用变量 - name: create new dir file: name={{ dir }} state=directory #引用变量 # 执行playbook [root@ansible PlayBook]# ansible-playbook variables.yml # 如果执行时候又重新指定了变量的值，那么会已重新指定的为准 [root@ansible PlayBook]# ansible-playbook -e "dir=/tmp/test2" variables.yml 调用setup模块获取变量setup模块默认是获取主机信息的，有时候在playbook中需要用到，所以可以直接调用。常用的参数参考# 编辑playbook文件 [root@ansible PlayBook]# cat variables.yml --- - hosts: all remote_user: root tasks: - name: create file file: name={{ ansible_fqdn }}.log state=touch #引用setup中的ansible_fqdn # 执行playbook [root@ansible PlayBook]# ansible-playbook variables.yml 独立的变量YAML文件中定义为了方便管理将所有的变量统一放在一个独立的变量YAML文件中，laybook文件直接引用文件调用变量即可。# 定义存放变量的文件 [root@ansible PlayBook]# cat var.yml var1: vsftpd var2: httpd # 编写playbook [root@ansible PlayBook]# cat variables.yml --- - hosts: all remote_user: root vars_files: #引用变量文件 - ./var.yml #指定变量文件的path（这里可以是绝对路径，也可以是相对路径） tasks: - name: install package yum: name={{ var1 }} #引用变量 - name: create file file: name=/tmp/{{ var2 }}.log state=touch #引用变量 # 执行playbook [root@ansible PlayBook]# ansible-playbook variables.yml Playbook中标签的使用一个playbook文件中，执行时如果想执行某一个任务，那么可以给每个任务集进行打标签，这样在执行的时候可以通过-t选择指定标签执行，还可以通过--skip-tags选择除了某个标签外全部执行等。# 编辑playbook [root@ansible PlayBook]# cat httpd.yml --- - hosts: 192.168.1.31 remote_user: root tasks: - name: install httpd yum: name=httpd state=installed tags: inhttpd - name: start httpd service: name=httpd state=started tags: sthttpd - name: restart httpd service: name=httpd state=restarted tags: - rshttpd - rs_httpd # 正常执行的结果 [root@ansible PlayBook]# ansible-playbook httpd.yml PLAY [192.168.1.31] ************************************************************************************************************************** TASK [Gathering Facts] *********************************************************************************************************************** ok: [192.168.1.31] TASK [install httpd] ************************************************************************************************************************* ok: [192.168.1.31] TASK [start httpd] *************************************************************************************************************************** ok: [192.168.1.31] TASK [restart httpd] ************************************************************************************************************************* changed: [192.168.1.31] PLAY RECAP *********************************************************************************************************************************** 192.168.1.31 : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 1）通过-t选项指定tags进行执行# 通过-t指定tags名称，多个tags用逗号隔开 [root@ansible PlayBook]# ansible-playbook -t rshttpd httpd.yml PLAY [192.168.1.31] ************************************************************************************************************************** TASK [Gathering Facts] *********************************************************************************************************************** ok: [192.168.1.31] TASK [restart httpd] ************************************************************************************************************************* changed: [192.168.1.31] PLAY RECAP *********************************************************************************************************************************** 192.168.1.31 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 2）通过--skip-tags选项排除不执行的tags[root@ansible PlayBook]# ansible-playbook --skip-tags inhttpd httpd.yml PLAY [192.168.1.31] ************************************************************************************************************************** TASK [Gathering Facts] *********************************************************************************************************************** ok: [192.168.1.31] TASK [start httpd] *************************************************************************************************************************** ok: [192.168.1.31] TASK [restart httpd] ************************************************************************************************************************* changed: [192.168.1.31] PLAY RECAP *********************************************************************************************************************************** 192.168.1.31 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Playbook中模板的使用template模板为我们提供了动态配置服务，使用jinja2语言，里面支持多种条件判断、循环、逻辑运算、比较操作等。其实说白了也就是一个文件，和之前配置文件使用copy一样，只是使用copy，不能根据服务器配置不一样进行不同动态的配置。这样就不利于管理。说明：1、多数情况下都将template文件放在和playbook文件同级的templates目录下（手动创建），这样playbook文件中可以直接引用，会自动去找这个文件。如果放在别的地方，也可以通过绝对路径去指定。2、模板文件后缀名为.j2。循环参考示例：通过template安装httpd1）playbook文件编写[root@ansible PlayBook]# cat testtmp.yml #模板示例 --- - hosts: all remote_user: root vars: - listen_port: 88 #定义变量 tasks: - name: Install Httpd yum: name=httpd state=installed - name: Config Httpd template: src=httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf #使用模板 notify: Restart Httpd - name: Start Httpd service: name=httpd state=started handlers: - name: Restart Httpd service: name=httpd state=restarted 2）模板文件准备，httpd配置文件准备，这里配置文件端口使用了变量[root@ansible PlayBook]# cat templates/httpd.conf.j2 |grep ^Listen Listen {{ listen_port }} 3）查看目录结构# 目录结构 [root@ansible PlayBook]# tree . . ├── templates │ └── httpd.conf.j2 └── testtmp.yml 1 directory, 2 files 4）执行playbook，由于192.168.1.36那台机器是6的系统，模板文件里面的配置文件是7上面默认的httpd配置文件，httpd版本不一样(6默认版本为2.2.15，7默认版本为2.4.6)，所以拷贝过去后启动报错。下面使用playbook中的判断语句进行处理；此处先略过[root@ansible PlayBook]# ansible-playbook testtmp.yml PLAY [all] ****************************************************************************************** TASK [Gathering Facts] ****************************************************************************** ok: [192.168.1.36] ok: [192.168.1.32] ok: [192.168.1.33] ok: [192.168.1.31] TASK [Install Httpd] ******************************************************************************** ok: [192.168.1.36] ok: [192.168.1.33] ok: [192.168.1.32] ok: [192.168.1.31] TASK [Config Httpd] ********************************************************************************* changed: [192.168.1.31] changed: [192.168.1.33] changed: [192.168.1.32] changed: [192.168.1.36] TASK [Start Httpd] ********************************************************************************** fatal: [192.168.1.36]: FAILED! => {"changed": false, "msg": "httpd: Syntax error on line 56 of /etc/httpd/conf/httpd.conf: Include directory '/etc/httpd/conf.modules.d' not found\n"} changed: [192.168.1.32] changed: [192.168.1.33] changed: [192.168.1.31] RUNNING HANDLER [Restart Httpd] ********************************************************************* changed: [192.168.1.31] changed: [192.168.1.32] changed: [192.168.1.33] PLAY RECAP ****************************************************************************************** 192.168.1.31 : ok=5 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.1.32 : ok=5 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.1.33 : ok=5 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.1.36 : ok=3 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 template之whenwhen语句参考条件测试：如果需要根据变量、facts或此前任务的执行结果来做为某task执行与否的前提时要用到条件测试，通过when语句执行，在task中使用jinja2的语法格式、when语句：在task后添加when子句即可使用条件测试；when语句支持jinja2表达式语法。类似这样：tasks: - command: /bin/false register: result ignore_errors: True - command: /bin/something when: result|failed - command: /bin/something_else when: result|success - command: /bin/still/something_else when: result|skipped 示例：通过when语句完善上面的httpd配置1）准备两个配置文件，一个centos6系统httpd配置文件，一个centos7系统httpd配置文件。[root@ansible PlayBook]# tree templates/ templates/ ├── httpd6.conf.j2 #6系统2.2.15版本httpd配置文件 └── httpd7.conf.j2 #7系统2.4.6版本httpd配置文件 0 directories, 2 files 2）修改playbook文件，通过setup模块获取系统版本去判断。setup常用模块[root@ansible PlayBook]# cat testtmp.yml #when示例 --- - hosts: all remote_user: root vars: - listen_port: 88 tasks: - name: Install Httpd yum: name=httpd state=installed - name: Config System6 Httpd template: src=httpd6.conf.j2 dest=/etc/httpd/conf/httpd.conf when: ansible_distribution_major_version == "6" #判断系统版本，为6便执行上面的template配置6的配置文件 notify: Restart Httpd - name: Config System7 Httpd template: src=httpd7.conf.j2 dest=/etc/httpd/conf/httpd.conf when: ansible_distribution_major_version == "7" #判断系统版本，为7便执行上面的template配置7的配置文件 notify: Restart Httpd - name: Start Httpd service: name=httpd state=started handlers: - name: Restart Httpd service: name=httpd state=restarted 3）执行playbook[root@ansible PlayBook]# ansible-playbook testtmp.yml PLAY [all] ****************************************************************************************** TASK [Gathering Facts] ****************************************************************************** ok: [192.168.1.31] ok: [192.168.1.32] ok: [192.168.1.33] ok: [192.168.1.36] TASK [Install Httpd] ******************************************************************************** ok: [192.168.1.32] ok: [192.168.1.33] ok: [192.168.1.31] ok: [192.168.1.36] TASK [Config System6 Httpd] ************************************************************************* skipping: [192.168.1.33] skipping: [192.168.1.31] skipping: [192.168.1.32] changed: [192.168.1.36] TASK [Config System7 Httpd] ************************************************************************* skipping: [192.168.1.36] changed: [192.168.1.33] changed: [192.168.1.31] changed: [192.168.1.32] TASK [Start Httpd] ********************************************************************************** ok: [192.168.1.36] ok: [192.168.1.31] ok: [192.168.1.32] ok: [192.168.1.33] RUNNING HANDLER [Restart Httpd] ********************************************************************* changed: [192.168.1.33] changed: [192.168.1.31] changed: [192.168.1.32] changed: [192.168.1.36] PLAY RECAP ****************************************************************************************** 192.168.1.31 : ok=5 changed=2 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 192.168.1.32 : ok=5 changed=2 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 192.168.1.33 : ok=5 changed=2 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 192.168.1.36 : ok=5 changed=2 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 template之with_itemswith_items迭代，当有需要重复性执行的任务时，可以使用迭代机制。对迭代项的引用，固定变量名为“item”，要在task中使用with_items给定要迭代的元素列表。列表格式：字符串字典示例1：通过with_items安装多个不同软件编写playbook[root@ansible PlayBook]# cat testwith.yml # 示例with_items --- - hosts: all remote_user: root tasks: - name: Install Package yum: name={{ item }} state=installed #引用item获取值 with_items: #定义with_items - httpd - vsftpd - nginx 上面tasks写法等同于：--- - hosts: all remote_user: root tasks: - name: Install Httpd yum: name=httpd state=installed - name: Install Vsftpd yum: name=vsftpd state=installed - name: Install Nginx yum: name=nginx state=installed 示例2：通过嵌套子变量创建用户并加入不同的组1）编写playbook[root@ansible PlayBook]# cat testwith01.yml # 示例with_items嵌套子变量 --- - hosts: all remote_user: root tasks: - name: Create New Group group: name={{ item }} state=present with_items: - group1 - group2 - group3 - name: Create New User user: name={{ item.name }} group={{ item.group }} state=present with_items: - { name: 'user1', group: 'group1' } - { name: 'user2', group: 'group2' } - { name: 'user3', group: 'group3' } 2）执行playbook并验证# 执行playbook [root@ansible PlayBook]# ansible-playbook testwith01.yml # 验证是否成功创建用户及组 [root@ansible PlayBook]# ansible all -m shell -a 'tail -3 /etc/passwd' 192.168.1.36 | CHANGED | rc=0 >> user1:x:500:500::/home/user1:/bin/bash user2:x:501:501::/home/user2:/bin/bash user3:x:502:502::/home/user3:/bin/bash 192.168.1.32 | CHANGED | rc=0 >> user1:x:1001:1001::/home/user1:/bin/bash user2:x:1002:1002::/home/user2:/bin/bash user3:x:1003:1003::/home/user3:/bin/bash 192.168.1.31 | CHANGED | rc=0 >> user1:x:1002:1003::/home/user1:/bin/bash user2:x:1003:1004::/home/user2:/bin/bash user3:x:1004:1005::/home/user3:/bin/bash 192.168.1.33 | CHANGED | rc=0 >> user1:x:1001:1001::/home/user1:/bin/bash user2:x:1002:1002::/home/user2:/bin/bash user3:x:1003:1003::/home/user3:/bin/bash template之for if通过使用for，if可以更加灵活的生成配置文件等需求，还可以在里面根据各种条件进行判断，然后生成不同的配置文件、或者服务器配置相关等。示例11）编写playbook[root@ansible PlayBook]# cat testfor01.yml # template for 示例 --- - hosts: all remote_user: root vars: nginx_vhost_port: - 81 - 82 - 83 tasks: - name: Templage Nginx Config template: src=nginx.conf.j2 dest=/tmp/nginx_test.conf 2）模板文件编写# 循环playbook文件中定义的变量，依次赋值给port [root@ansible PlayBook]# cat templates/nginx.conf.j2 {% for port in nginx_vhost_port %} server{ listen: {{ port }}; server_name: localhost; } {% endfor %} 3）执行playbook并查看生成结果[root@ansible PlayBook]# ansible-playbook testfor01.yml # 去到一个节点看下生成的结果发现自动生成了三个虚拟主机 [root@linux ~]# cat /tmp/nginx_test.conf server{ listen: 81; server_name: localhost; } server{ listen: 82; server_name: localhost; } server{ listen: 83; server_name: localhost; } 示例21）编写playbook[root@ansible PlayBook]# cat testfor02.yml # template for 示例 --- - hosts: all remote_user: root vars: nginx_vhosts: - web1: listen: 8081 server_name: "web1.example.com" root: "/var/www/nginx/web1" - web2: listen: 8082 server_name: "web2.example.com" root: "/var/www/nginx/web2" - web3: listen: 8083 server_name: "web3.example.com" root: "/var/www/nginx/web3" tasks: - name: Templage Nginx Config template: src=nginx.conf.j2 dest=/tmp/nginx_vhost.conf 2）模板文件编写[root@ansible PlayBook]# cat templates/nginx.conf.j2 {% for vhost in nginx_vhosts %} server{ listen: {{ vhost.listen }}; server_name: {{ vhost.server_name }}; root: {{ vhost.root }}; } {% endfor %} 3）执行playbook并查看生成结果[root@ansible PlayBook]# ansible-playbook testfor02.yml # 去到一个节点看下生成的结果发现自动生成了三个虚拟主机 [root@linux ~]# cat /tmp/nginx_vhost.conf server{ listen: 8081; server_name: web1.example.com; root: /var/www/nginx/web1; } server{ listen: 8082; server_name: web2.example.com; root: /var/www/nginx/web2; } server{ listen: 8083; server_name: web3.example.com; root: /var/www/nginx/web3; } 示例3在for循环中再嵌套if判断，让生成的配置文件更加灵活1）编写playbook[root@ansible PlayBook]# cat testfor03.yml # template for 示例 --- - hosts: all remote_user: root vars: nginx_vhosts: - web1: listen: 8081 root: "/var/www/nginx/web1" - web2: server_name: "web2.example.com" root: "/var/www/nginx/web2" - web3: listen: 8083 server_name: "web3.example.com" root: "/var/www/nginx/web3" tasks: - name: Templage Nginx Config template: src=nginx.conf.j2 dest=/tmp/nginx_vhost.conf 2）模板文件编写# 说明：这里添加了判断，如果listen没有定义的话，默认端口使用8888，如果server_name有定义，那么生成的配置文件中才有这一项。 [root@ansible PlayBook]# cat templates/nginx.conf.j2 {% for vhost in nginx_vhosts %} server{ {% if vhost.listen is defined %} listen: {{ vhost.listen }}; {% else %} listen: 8888; {% endif %} {% if vhost.server_name is defined %} server_name: {{ vhost.server_name }}; {% endif %} root: {{ vhost.root }}; } {% endfor %} 3）执行playbook并查看生成结果[root@ansible PlayBook]# ansible-playbook testfor03.yml # 去到一个节点看下生成的结果发现自动生成了三个虚拟主机 [root@linux ~]# cat /tmp/nginx_vhost.conf server{ listen: 8081; root: /var/www/nginx/web1; } server{ listen: 8888; server_name: web2.example.com; root: /var/www/nginx/web2; } server{ listen: 8083; server_name: web3.example.com; root: /var/www/nginx/web3; } 上面三个示例的图片展示效果例一例二例三作者：别来无恙-出处：https://www.cnblogs.com/yanjieli/p/10969299.html版权：本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。