Ansible学习实战手记-你想要知道的可能都在这里了
最近接触了ansible工具,查找了一些资料,也做了一些总结。希望能给刚接触的新手带来一些帮助。
此总结有实际例子,大部分也是从实践中用到才逐一总结的。
当然可能肯定一定会存在一些错误和纰漏,还望大家具体实践时进一步熟悉了解。
ansible本身的模块有几百个,按照官网的建议来说,不建议一次性学完。
我们需要一边学习一边实践一边总结。要经常查找官方文档。
官方文档如下:
https://docs.ansible.com/ansible/latest/index.html
Ansible学习 安装: pip install ansible==2.4.1.0 为什么要指定版本是:2.4.1.0 1、因为最新版本2.7.1在导入时,报错:root@ubuntu:/etc/ansible# ansible --version Traceback (most recent call last):File "/usr/bin/ansible", line 41, in <module>from ansible.utils.unicode import to_unicodeImportError: cannot import name to_unicode 暂时解决办法:将ansible版本降低到2.4.1.0问题解决 2、一些新特性只能在更高的ansible版本中使用,比如:include_tasks在低版本中使用时会报错:ERROR! no action detected in taskThe error appears to have been in '/etc/ansible/roles/newone/tasks/main.yml': line 8, column 4, but maybe elsewhere in the file depending on the exact syntax problem.The offending line appears to be:- include_tasks: lala.yml^ here依赖包如下: jinja2 Jinja2 2.10 PyYAML PyYAML 3.11 paramiko paramiko 2.4.2 cryptography cryptography 2.3.1 setuptools setuptools 20.7.0 MarkupSafe>=0.23 MarkupSafe 0.23 pyasn1>=0.1.7 pyasn1 0.4.4 bcrypt>=3.1.3 bcrypt 3.1.4 pynacl>=1.0.1 PyNaCl 1.3.0 enum34; python_version < "3" enum34 1.1.6 asn1crypto>=0.21.0 asn1crypto 0.24.0 cffi!=1.11.3,>=1.7 cffi 1.11.5 idna>=2.1 idna 2.7 six>=1.4.1 six 1.10.0 ipaddress; python_version < "3" ipaddress 1.0.22 pycparser pycparser 2.19ansible -h 参数解析 Usage: ansible <host-pattern> [options]Options:-a MODULE_ARGS, --args=MODULE_ARGS 模块的参数,如果执行默认COMMAND的模块,即是命令参数,如:“date”,"pwd"等等module arguments 模块参数-k, --ask-pass ask for SSH password 登录密码,提示输入SSH密码而不是假设基于密钥的验证--ask-su-pass ask for su password su切换密码-K, --ask-sudo-pass ask for sudo password 提示密码使用sudo,sudo表示提权操作--ask-vault-pass ask for vault password-B SECONDS, --background=SECONDS 后台运行超时时间run asynchronously, failing after X seconds(default=N/A)-C, --check don't make any changes; instead, try to predict some 只是测试一下会改变什么内容,不会真正去执行;相反,试图预测一些可能发生的变化 of the changes that may occur-c CONNECTION, --connection=CONNECTION 连接类型使用。可能的选项是paramiko(SSH),SSH和地方。当地主要是用于crontab或启动。connection type to use (default=smart)-f FORKS, --forks=FORKS 并行任务数。NUM被指定为一个整数,默认是5specify number of parallel processes to use(default=5)-h, --help show this help message and exit 打开帮助文档API-i INVENTORY, --inventory-file=INVENTORY 指定库存主机文件的路径,默认为/etc/ansible/hostsspecify inventory host file(default=/etc/ansible/hosts)-l SUBSET, --limit=SUBSET 进一步限制所选主机/组模式 --limit=192.168.91.135 只对这个ip执行further limit selected hosts to an additional pattern--list-hosts outputs a list of matching hosts; does not executeanything else-m MODULE_NAME, --module-name=MODULE_NAME 执行模块的名字,默认使用 command 模块,所以如果是只执行单一命令可以不用 -m参数module name to execute (default=command)-M MODULE_PATH, --module-path=MODULE_PATH 要执行的模块的路径,默认为/usr/share/ansible/specify path(s) to module library(default=/usr/share/ansible/)-o, --one-line condense output 压缩输出,摘要输出.尝试一切都在一行上输出。-P POLL_INTERVAL, --poll=POLL_INTERVAL 调查背景工作每隔数秒。需要- bset the poll interval if using -B (default=15)--private-key=PRIVATE_KEY_FILE 私钥路径,使用这个文件来验证连接use this file to authenticate the connection-S, --su run operations with su 用 su 命令-R SU_USER, --su-user=SU_USER 指定SU的用户,默认是root用户run operations with su as this user (default=root)-s, --sudo run operations with sudo (nopasswd) -U SUDO_USER, --sudo-user=SUDO_USER sudo到哪个用户,默认为 root desired sudo user (default=root)-T TIMEOUT, --timeout=TIMEOUT 指定SSH默认超时时间, 默认是10Soverride the SSH timeout in seconds (default=10)-t TREE, --tree=TREE log output to this directory 将日志内容保存在该输出目录,结果保存在一个文件中在每台主机上。-u REMOTE_USER, --user=REMOTE_USER 远程用户, 默认是root用户connect as this user (default=root)--vault-password-file=VAULT_PASSWORD_FILE vault password file-v, --verbose verbose mode (-vvv for more, -vvvv to enable 如果命令执行成功,输出详细的结果connection debugging)(-
vv –vvv
-
vvvv)
--version show program's version number and exit 输出ansible的版本 ansible-playbook参数解析: Options:--ask-vault-pass #加密playbook文件时提示输入密码-C, --check #模拟执行,不会真正在机器上执行(查看执行会产生什么变化)-D, --diff #当更新的文件数及内容较少时,该选项可显示这些文件不同的地方,该选项结合-C用会有较好的效果-e EXTRA_VARS, --extra-vars=EXTRA_VARS#在Playbook中引入外部参数变量--flush-cache #将fact清除到的远程主机缓存--force-handlers #强制运行handlers的任务,即使在任务失败的情况下-f FORKS, --forks=FORKS#并行任务数。FORKS被指定为一个整数,默认是5-h, --help #打开帮助文档API-i INVENTORY, --inventory-file=INVENTORY#specify inventory host path (default=/etc/ansible/hosts) or comma separated host list.#指定要读取的Inventory文件-l SUBSET, --limit=SUBSET#further limit selected hosts to an additional pattern#限定执行的主机范围--list-hosts #outputs a list of matching hosts; does not execute anything else#列出执行匹配到的主机,但并不会执行--list-tags #list all available tags#列出所有可用的tags--list-tasks #list all tasks that would be executed#列出所有即将被执行的任务-M MODULE_PATH, --module-path=MODULE_PATH#specify path(s) to module library (default=None)#要执行的模块的路径--new-vault-password-file=NEW_VAULT_PASSWORD_FILE#new vault password file for rekey# --output=OUTPUT_FILE #output file name for encrypt or decrypt; use - for stdout# --skip-tags=SKIP_TAGS#only run plays and tasks whose tags do not match these values#跳过指定的tags任务--start-at-task=START_AT_TASK#start the playbook at the task matching this name#从第几条任务(START_AT_TASK)开始执行--step #one-step-at-a-time: confirm each task before running#逐步执行Playbook定义的任务,并经人工确认后继续执行下一步任务--syntax-check #perform a syntax check on the playbook, but do not execute it#检查Playbook中的语法书写,并不实际执行-t TAGS, --tags=TAGS #only run plays and tasks tagged with these values#指定执行该tags的任务--vault-password-file=VAULT_PASSWORD_FILE#vault password file# -v, --verbose #verbose mode (-vvv for more, -vvvv to enable connection debugging)#执行详细输出--version #show program's version number and exit#显示版本 Connection Options:control as whom and how to connect to hosts-k, --ask-pass #ask for connection password# --private-key=PRIVATE_KEY_FILE, --key-file=PRIVATE_KEY_FILE#use this file to authenticate the connection# -u REMOTE_USER, --user=REMOTE_USER#connect as this user (default=None)#指定远程主机以USERNAME运行命令-c CONNECTION, --connection=CONNECTION#connection type to use (default=smart)#指定连接方式,可用选项paramiko (SSH)、ssh、local,local方式常用于crontab和kickstarts-T TIMEOUT, --timeout=TIMEOUT#override the connection timeout in seconds(default=10)#SSH连接超时时间设定,默认10s--ssh-common-args=SSH_COMMON_ARGS#specify common arguments to pass to sftp/scp/ssh (e.g.ProxyCommand)# --sftp-extra-args=SFTP_EXTRA_ARGS#specify extra arguments to pass to sftp only (e.g. -f, -l)# --scp-extra-args=SCP_EXTRA_ARGS#specify extra arguments to pass to scp only (e.g. -l)# --ssh-extra-args=SSH_EXTRA_ARGS#specify extra arguments to pass to ssh only (e.g. -R)# Privilege Escalation Options:control how and which user you become as on target hosts-s, --sudo #run operations with sudo (nopasswd) (deprecated, use become)#相当于Linux系统下的sudo命令-U SUDO_USER, --sudo-user=SUDO_USER#desired sudo user (default=root) (deprecated, use become)#使用sudo,相当于Linux下的sudo命令-S, --su #run operations with su (deprecated, use become)# -R SU_USER, --su-user=SU_USER#run operations with su as this user (default=root)(deprecated, use become)-b, --become #run operations with become (does not imply password prompting)# --become-method=BECOME_METHOD#privilege escalation method to use (default=sudo),valid choices: [ sudo | su | pbrun | pfexec | doas |dzdo | ksu | runas ]# --become-user=BECOME_USER#run operations as this user (default=root)# --ask-sudo-pass #ask for sudo password (deprecated, use become)#传递sudo密码到远程主机,来保证sudo命令的正常运行--ask-su-pass #ask for su password (deprecated, use become)# -K, --ask-become-pass#ask for privilege escalation password# 当然,我们对于一些具体的学习还要参考一些文档 https://docs.ansible.com/ansible/2.4/intro_installation.htmlAnsible学习实践: 1.在A主机上创建密钥对,实现对其他主机无密码访问,执行: # ssh-keygen -t rsa -f ~/.ssh/id_rsa.pub -P "" # ssh-copy-id -i /root/.ssh/id_rsa.pub root@172.18.19.188 此时会在远程机的/root/.ssh/authorized_keys文件中,生成id_rsa.pub文件的内容。执行具体回显 root@docker-02:~# ssh-copy-id -i /root/.ssh/id_rsa.pub root@172.18.19.188 /usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub" The authenticity of host '172.18.19.188 (172.18.19.188)' can't be established. ECDSA key fingerprint is SHA256:BLDdJTy5lNOuopbtXDVojySMfc1y2lmJSPwvKIyvSVM. Are you sure you want to continue connecting (yes/no)? yes /usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed /usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys root@172.18.19.188's password: Number of key(s) added: 1Now try logging into the machine, with: "ssh 'root@172.18.19.188'" and check to make sure that only the key(s) you wanted were added.root@docker-02:~# ssh 'root@172.18.19.188' Welcome to Ubuntu 16.04 LTS (GNU/Linux 4.4.0-21-generic x86_64)* Documentation: https://help.ubuntu.com/177 packages can be updated. 24 updates are security updates.*** System restart required *** Last login: Wed Nov 22 19:39:52 2017 from 172.18.19.94 root@docker-01:~# exit logout Connection to 172.18.19.188 closed.2.A主机管理清单的配置# cd /etc/ansible# cp hosts{,.bak}# vim hosts 添加如下内容: [remote] 172.18.19.188 测试执行是否成功: # ansible remote -m command -a "ls" 172.18.19.188 | SUCCESS | rc=0 >> # ansible all -m command -a "ls" 172.18.19.188 | SUCCESS | rc=0 >> 两种方法都可以解决执行问题。3.常用模块1.command模块:在远程主机上执行的命令相关选项:creates:一个文件名,当该文件存在,则该命令不执行free_form:要执行的linux指令chdir:在执行指令之前,先切换到该目录removes:一个文件名,当该文件不存在,则该选项不执行executable:切换shell来执行指令,该执行路径必须是一个绝对路径 /*示例*/ ansible remote -m command -a "ls"值得留意的时,command模块执行的命令是获取不到$HOME这样的环境变量的,一些运算符,例如”<“ 、”>“ 在command模块上也是不能使用的。2.setup模块:查看远程主机的相关facts变量信息 /*示例*/ ansible all -m setup ansible 192.168.43.130 -m setup3.shell模块:让远程主机在shell进程下执行命令,从而支持shell的特性,如管道等 /*示例*/ ansible all -m shell -a "echo "test" | passwd --stdin test1"相当于增强版的command4.copy模块:复制本地文件至远程主机上相关选项:backup:在覆盖之前,将源文件备份,备份文件包含时间信息。有两个选项:yes|nocontent:用于替代“src”,可以直接设定指定文件的值dest:必选项。要将源文件复制到的远程主机的绝对路径,如果源文件是一个目录,那么该路径也必须是个目录directory_mode:递归设定目录的权限,默认为系统默认权限force:如果目标主机包含该文件,但内容不同,如果设置为yes,则强制覆盖,如果为no,则只有当目标主机的 目标位置不存在该文件时,才复制。默认为yesothers:所有的file模块里的选项都可以在这里使用src:被复制到远程主机的本地文件,可以是绝对路径,也可以是相对路径。如果路径是一个目录,它将递归复制。 在这种情况下,如果路径使用“/”来结尾,则只复制目录里的内容,如果没有使用“/”来结尾, 则包含目录在内的整个内容全部复制,类似于rsync。owner,group,mode... /*示例*/ ansible remote -m copy -a "src=/etc/fstab dest=/root/ owner=root group=root mode=0644"5.file模块:设置文件属性相关选项:force:需要在两种情况下强制创建软链接,一种是源文件不存在,但之后会建立的情况下;另一种是目标软链接已存在, 需要先取消之前的软链,然后创建新的软链,有两个选项:yes|nogroup:定义文件/目录的属组mode:定义文件/目录的权限owner:定义文件/目录的属主path:必选项,定义文件/目录的路径recurse:递归设置文件的属性,只对目录有效src:被链接的源文件路径,只应用于state=link的情况dest:被链接到的路径,只应用于state=link的情况state:directory:如果目录不存在,就创建目录file:即使文件不存在,也不会被创建link:创建软链接hard:创建硬链接touch:如果文件不存在,则会创建一个新的文件,如果文件或目录已存在,则更新其最后修改时间absent:删除目录、文件或者取消链接文件 /*示例*/ ansible remote -m file -a "path=/root/fstab owner=root group=root mode=600" ansible storm_cluster -m file -a "src=/etc/resolv.conf dest=/tmp/resolv.conf state=link"创建链接文件 ansible storm_cluster -m file -a "path=/tmp/resolv.conf state=absent" 删除链接文件6.cron模块:计划任务的实现相关选项:minute=/hour=/day=/month=/weekday= 某个值不写,默认就是*name:必选项,任务描述信息job:执行的任务,要加引号state:present(创建)/absent(删除) /*示例*/ ansible remote -m cron -a "minute=*/1 job='/usr/bin/echo 'hello'' name=hello"7.yum模块:管理安装相关程序包相关选项:name:程序包名称,可带版本号state:present、installed、latest(安装)/absent、removed(删除)8.service模块:管理服务相关选项:name:服务名称state:started/stopped/restartedenabled:true/falserunlevel:运行级别9.group模块:管理用户组模块相关选项:name:组名称gid:指定GIDstate:present/absentsystem:yes/no /*示例*/ ansible all -m group -a "name=test_grp state=present"10.user模块:管理用户模块相关选项:由于user模块的选项众多,这里只介绍一些常用的选项:name:用户名password:为用户设置登陆密码,此密码是明文密码加密后的密码update_password:always/on_createalways:只有当密码不相同时才会更新密码(默认)on_create:只为新用户设置密码shell:用户的shell设定groups:用户组设定home:指定用户的家目录state:present/absentappend:yes/noyes:增量添加groupno:全量变更group,只设置groups指定的group组(默认)remove:配合state=absent使用,删除用户的家目录->remove=yesexpires:设置用户的过期时间,值是一个时间戳 /*示例*/ ansible all -m user -a "name=test2 state=present groups=test2,test_grp shell=/bin/bash append=yes"11.ping 用来测试远程主机的运行状态 /*示例*/ ansible all -m ping 172.18.19.188 | SUCCESS => {"changed": false, "ping": "pong" }ansible-doc Usage: ansible-doc [-l|-s] [options] [-t <plugin type] [plugin]plugin documentation toolOptions:-a, --all **For internal testing only** Show documentation forall plugins.-h, --help show this help message and exit-l, --list List available plugins-M MODULE_PATH, --module-path=MODULE_PATHprepend colon-separated path(s) to module library(default=[u'/root/.ansible/plugins/modules',u'/usr/share/ansible/plugins/modules'])-s, --snippet Show playbook snippet for specified plugin(s)-t TYPE, --type=TYPE Choose which plugin type (defaults to "module")-v, --verbose verbose mode (-vvv for more, -vvvv to enableconnection debugging)--version show program's version number and exit ansible-doc command ===>会打印出command模块的使用帮助4.Playbook playbook是由一个或多个“play”组成的列表,可以让它们联同起来按事先编排的机制执行;所谓task无非是调用ansible的 一个module,而在模块参数中可以使用变量;模块执行是幂等的,这意味着多次执行是安全的,因为其结果均一致。 执行模型:task list中的各任务按次序逐个在hosts中指定的所有主机上执行,即在所有主机上完成第一个任务后再开始第二个。 在顺序运行某playbook时,如果中途发生错误,所有已执行任务都将回滚,因此,在修改playbook后重新执行一次即可; task组成:每个task都应该有其name,用于playbook的执行结果输出,建议其内容尽可能清晰地描述任务执行步骤。如果未提供name,则action的结果将用于输出; notify指定handler的执行机制:“notify”这个action可用于在每个play的最后被触发,在notify中列出的操作称为handler,仅在所有的变化发生完成后一次性地执行指定操作。 5.实践ansible自动化安装nginx 首先、配置 /etc/hosts: IP test 第二、配置 /etc/ansible/hosts: [hadoop] test第三、创建目录 mkdir -p /ansible/roles/nginx/{defaults,files,handlers,meta,tasks,templates,vars}第四、编辑文件 在files目录下创建shell安装脚本,并将nginx的压缩包也放到files下面 install_nginx.sh:#!/bin/bash yum -y install zlib zlib-devel openssl openssl-devel pcre-devel groupadd -r nginx useradd -s /sbin/nologin -g nginx -r nginx cd /tmp tar xf nginx-1.9.9.tar.gz;cd nginx-1.9.9 mkdir /var/run/nginx/;chown nginx.nginx /var/run/nginx/ ./configure \ --prefix=/usr \ --sbin-path=/usr/sbin/nginx \ --conf-path=/etc/nginx/nginx.conf \ --error-log-path=/var/log/nginx/error.log \ --pid-path=/var/run/nginx/nginx.pid \ --user=nginx \ --group=nginx \ --with-http_ssl_module make && make install sed "/^\s*index / i proxy_pass http://localhost:8080;" /etc/nginx/nginx.conf /usr/sbin/nginx 在tasks目录中放置main.yml文件 main.yml:- name: copy nginx_tar_gz to clientcopy: src=nginx-1.9.9.tar.gz dest=/tmp/nginx-1.9.9.tar.gz - name: copy install_shell to clientcopy: src=install_nginx.sh dest=/tmp/install_nginx.sh - name: install nginxshell: /bin/bash /tmp/install_nginx.sh在ansible目录下放置webservice.yml文件 webservice.yml:- hosts: hadoopremote_user: rootroles:- nginx第五、执行 cd /ansible ansible-playbook webservice.yml目录结构解析如下: roles/ \\ ansible所有的信息都放到此目录下面对应的目录中 └── nginx \\ 角色名称├── default \\ 为当前角色设定默认变量时使用此目录,应当包含一个main.yml文件;├── files \\ 存放有copy或script等模块调用的文件├── handlers \\ 此目录应当包含一个main.yml文件,用于定义各角色用到的各handler├── meta \\ 应当包含一个main.yml,用于定义角色的特殊设定及其依赖关系;1.3及以后版本支持├── tasks \\ 至少包含一个名为main.yml的文件,定义了此角色的任务列表,可使用include指令├── templates \\ template模块会自动在此目录中寻找Jinja2模板文件└── vars \\ 应当包含一个main.yml文件,用于定义此角色用到的变量 ├───────────├──────────────────────────────────────────────────────────────────│ │ 目录名 │ 说明 │ ├───────────├──────────────────────────────────────────────────────────────────│ ├defaults │ 为当前角色设定默认变量时使用此目录,应当包含一个main.yml文件 │ ├───────────├──────────────────────────────────────────────────────────────────│ ├handlers │此目录中应当包含一个main.yml文件,用于定义此角色用到的各handler, │ │ │ 在handler中使用include包含的其它的handler文件也应该位于此目录中 │ ├───────────├──────────────────────────────────────────────────────────────────│ ├meta │ 应当包含一个main.yml文件,用于定义此角色的特殊设定及其依赖关系 │ ├───────────├──────────────────────────────────────────────────────────────────│ ├tasks │ 至少应该包含一个名为main.yml的文件,其定义了此角色的任务列表, │ │ │ 此文件可以使用include包含其它的位于此目录中的task文件 │ ├───────────├──────────────────────────────────────────────────────────────────│ ├templates │ template模块会自动在此目录中寻找Jinja2模板文件 │ ├───────────├──────────────────────────────────────────────────────────────────│ ├vars │ 定义当前角色使用的变量 │ ├───────────├──────────────────────────────────────────────────────────────────│ ├files │ 存放由copy或script等模块调用的文件 │ ├───────────├──────────────────────────────────────────────────────────────────│ ├tests │ 在playbook中角色的使用样例 │ ├───────────├──────────────────────────────────────────────────────────────────│--- - name: create userhosts: remoteremote_user: rootgather_facts: falsevars:- say: "tiger"tasks:- name: Copy file to client # copy: src=/tmp/tiger dest=/tmp/tigresstemplate: src=/tmp/tiger dest=/tmp/{{ say }}ansible-playbook -i /root/xxx.cfg /root/app/main.yml --limit "lala_xxx" -e "user=wawo"解析: -i 指定要运行的配置文件 --limit 指定运行的ip地址 -e 指定运行的外部参数 运行的控制 YAML 文件为: /root/app/main.yml --- - hosts: allroles:- xxxhosts指定所有(all)的主机,但是由于在外部已经指定了主机的配置,所以all由外部指定参数来进行 roles指定要执行的具体剧本 roles的任务执行顺序 ### 首先执行meta下的main.yml文件内容 可以设置该role和其它role之前的关联关系。 dependencies ### 然后执行tasks下的main.yml文件内容 ### 用到的变量,会直接加载defaults/vars目录下的main.yml文件 ### 用到的需要拷贝到远程机器的文件,会放到files目录下 ### 用到模板文件,会放到 templates 目录下 ### 在执行的task中,使用了notify后,会调用 handlers 目录下的main.yml文件 记录一些基本的使用模块 1、ansible中的include, include_tasks 和 import_tasks 的差别 include 被 deprecated(不建议使用)了. 建议使用 include_tasks 和 import_tasksinclude_tasks 是动态的: 在运行时展开. when只应用一次. 被include的文件名可以使用变量.import_tasks 是静态的: 在加载时展开. when在被import的文件里的每个task, 都会重新检查一次. 因为是加载时展开的, 文件名的变量不能是动态设定的. 请确保文件名中使用到的变量被定义在vars中、vars_files中、或者extra-vars中,静态的import不支持其他方式传入的变量。When using static includes, ensure that any variables used in their names are defined in vars/vars_files or extra-vars passed in from the command line. Static includes cannot use variables from inventory sources like group or host vars. 除了上述不同之处,在使用"循环操作"和"条件判断"时,"include_tasks"和"import_tasks"也有很多不同点需要注意,注意点如下。 如果想要对包含的任务列表进行循环操作,则只能使用"include_tasks"关键字,不能使用"import_tasks"关键字,"import_tasks"并不支持循环操作, 也就是说,使用"loop"关键字或"with_items"关键字对include文件进行循环操作时,只能配合"include_tasks"才能正常运行。 when关键字对"include_tasks"和"import_tasks"的实际操作有着本质区别,区别如下: 当对"include_tasks"使用when进行条件判断时,when对应的条件只会应用于"include_tasks"任务本身,当执行被包含的任务时,不会对这些被包含的任务重新进行条件判断。 当对"import_tasks"使用when进行条件判断时,when对应的条件会应用于被include的文件中的每一个任务,当执行被包含的任务时,会对每一个被包含的任务进行同样的条件判断。 对于tags和handler 与"include_tasks"不同,当为"import_tasks"添加标签时,tags是针对被包含文件中的所有任务生效的,与"include"关键字的效果相同。 "include_tasks"与"import_tasks"都可以在handlers中使用,并没有什么不同,不过在当前2.7.0版本中,如果在handlers中使用"import_tasks"引用任务列表,会出现bug,期待修复。tasks:- include_tasks:file: in.ymlapply:tags:- t1tags: always2、setup模块用于收集远程主机的一些基本信息。而在playbook中,默认参数 ” gather_facts: True ” 的含义就是在远程主机运行setup模块,并将收集的信息记录起来。 gather_facts: False 不使用远程主机的setup模块, tasks:- set_fact: mode=1 设置远程主机的参数 mode=1 3、一点疑惑 --- - hosts: webservervars: logserver: 10.127.2.170gather_facts: True tasks:- name: add conf to config files to CentOS6lineinfile: dest=/etc/rsyslog.conf line="*.* @{{ logserver }}"when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == "6"- name: restart syslog @CentOS6when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == "6"service: name=rsyslog state=restarted- name: add conf to config files to RedHat 5lineinfile: dest=/etc/syslog.conf line="*.* @{{ logserver }}"when: ansible_distribution == 'RedHat' and ansible_distribution_major_version == "5"- name: restart syslog @RedHat 5when: ansible_distribution == 'RedHat' and ansible_distribution_major_version == "5"service: name=syslog state=restarted 有同学要问,为什么要进行四次when判断,两次不就够了,写成这样- name: restart syslog @CentOS6when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == "6"lineinfile: dest=/etc/rsyslog.conf line="*.* @{{ logserver }}"service: name=rsyslog state=restarted 这是不行的,ansible要求每一个play里面只能使用一个模块,使用多个会报错 ERROR: multiple actions specified in task4、tasks/main.yml 里面有如下行:- name: Configure Tomcat servertemplate: src=server.xml dest=/usr/share/tomcat/conf/notify: restart tomcat- name: Configure Tomcat usertemplate: src=tomcat-users.xml dest=/usr/share/tomcat/conf/notify: restart tomcattemplate模块官方的解释为: Templates a file out to a remote server. 大概意思就是当 src=config_file 这些文件发生变化的时候,触发notify的动作 templates目录就是存放这些文件用的(一般都是一些配置文件)handlers目录里有一个main.yml文件,就是用来执行notify动作的大概的流程为:templates/config_file 发生变化 --> 触发notify: action --> action定义在 handlers/main.yml 中notify后面的动作名字必须与handlers/main.yml里面的name后面的名字一致,例:- name: Configure Tomcat usertemplate: src=tomcat-users.xml dest=/usr/share/tomcat/conf/notify: restart tomcathandlers:- name: restart tomcatservice: name=tomcat state=restart而files目录下存放的是一些脚本, 通过copy模块可以transport到remote hosts上的,而后触发notify动作之后执行的脚本5、语法验证 ● 在执行playbook之前,最后好进行验证,确保内容无误$ ansible-playbook --syntax-check site.ymlplaybook: site.yml ● 语法失败时将会报告错误(无法坚持模块内参数是否正确)6、执行空运行 ● -C选项。这会使ansible报告在执行该playbook时将会发生什么更改,但不会对受管主机进行任何实际更改$ ansible-playbook -C site.yml7、特权升级属性: ● 特提供额外的属性,从而在playbook内定义特权升级参数。 become布尔值参数可用于启动或禁用特权升级,无论在ansible配置文件如何定义become: Ture/False● 如果启用了特权升级,可以使用become_method属性来定义play期间所要使用的特权升级的方法sudobecome_method: sudo此外,启用特权升级时,become_user属性可以定义play上下文内用于特权升级的用户 become_user: root 8、用户属性: ● playbook中的任务通常通过网络连接多受管主机执行。与临时命令相同,用于这些任务执行的用户账号取决于ansible配置文件/etc/ansible/ansible.cfg中的参数。执行任务的用户可以通过remote_user参数定义,不过,如果启用了特权升级,become_user等其他参数也会发生作用 ● 如果用于任务执行的Ansible配置中定义的远程用户不合适,可以通过在play中使用remote_user属性覆盖remote_user: devops9、修改文件 lineinfile 用于检测文件是否存在特殊行或者使用后端正则表达式来替换匹配到的特殊行10、replace lineinfile的多行匹配版本,此模块会在文件中插入一段内容,并在内容开始和结束位置设置标签,后续可以使用标签可以对此块内容进行操作 path参数:必须参数,指定要操作的文件,2.3版本之前,只能使用dest, destfile, name指定要操作的文件,2.4版本中,仍然可以使用这些参数名,这些参数名作为path参数的别名使用。 regexp参数:必须参数,指定一个python正则表达式,文件中与正则匹配的字符串将会被替换。 replace参数: 指定最终要替换成的字符串。 backup参数:是否在修改文件之前对文件进行备份,最好设置为yes。### 在ml2_conf.ini文件的[ml2]和[ml2_type_vlan]字段之间插入一段内容 - name: Enable ovn in neutron-serverreplace:dest: "{{ node_config_directory }}/neutron-server/ml2_conf.ini"regexp: '\[ml2\][\S\s]*(?=\[ml2_type_vlan\])'replace: |+[ml2]type_drivers = local,flat,vlan,genevetenant_network_types = genevemechanism_drivers = ovnextension_drivers = port_securityoverlay_ip_version = 4[ml2_type_geneve]vni_ranges = 1:65536max_header_size = 38[ovn]ovn_nb_connection = tcp:{{ api_interface_address }}:{{ ovn_northdb_port }}ovn_sb_connection = tcp:{{ api_interface_address }}:{{ ovn_sourthdb_port }}ovn_l3_mode = Falseovn_l3_scheduler = chanceovn_native_dhcp = Trueneutron_sync_mode = repairbackup: yeswhen:- action == "deploy"- inventory_hostname in groups['network']notify:- Restart neutron-server container11、ini_file ini后缀格式文件修改 ini文件是十分常见的一种配置文件,ansible内置了ini配置文件的管理模块,用于对文件进行配置项的管理。 Ø 修改配置文件/root/demo.ini,selection为cron的选项组的crontime选项,把cron的值修改为10。 ansible all –m ini_file –a “dest=/root/demo.ini section=cron option=crontime value=10”### 设置l3_agent.ini文件[DEFAULT]字段的external_network_bridge选项值为br-ex - name: Set the external network bridgevars:agent: "{{ 'neutron-vpnaas-agent' if enable_neutron_vpnaas | bool else 'neutron-l3-agent' }}"ini_file:dest: "{{ node_config_directory }}/{{ agent }}/l3_agent.ini"section: "DEFAULT"option: "external_network_bridge"value: "{{ neutron_bridge_name | default('br-ex') }}"backup: yeswhen:- action == "deploy"- inventory_hostname in ovn_central_addressdelegate_to: "{{ item }}"with_items: "{{ groups['neutron-server'] }}"notify:- Restart {{ agent }} container12、循环控制 with_items 标准循环,用于执行重复任务,{{ item }}类似宏展开- name: add several usersuser:name: "{{ item.name }}"state: presentgroups: "{{ item.groups }}"with_items:- { name: 'testuser1', groups: 'wheel' }- { name: 'testuser2', groups: 'root' } with_nested 嵌套循环### 修改neutron-server组所有主机的ml2_conf.ini文件的对应字段值 - name: Enable ovn in neutron-servervars:params:- { section: 'ml2', option: 'type_drivers', value: 'local,flat,vlan,geneve' }- { section: 'ml2', option: 'tenant_network_types', value: 'geneve' }- { section: 'ml2', option: 'mechanism_drivers', value: 'ovn' }- { section: 'ml2', option: 'extension_drivers', value: 'port_security' }- { section: 'ml2', option: 'overlay_ip_version', value: '4' }- { section: 'securitygroup', option: 'enable_security_group', value: 'True' }ini_file:dest: "{{ node_config_directory }}/neutron-server/ml2_conf.ini"section: "{{ item[0].section }}"option: "{{ item[0].option }}"value: "{{ item[0].value }}"backup: yeswhen:- action == "deploy"- inventory_hostname in ovn_central_addressdelegate_to: "{{ item[1] }}"with_nested:- "{{ params }}"- "{{ groups['neutron-server'] }}"notify:- Restart neutron-server container13、流程控制 tags 设置任务标签tasks:- yum: name={{ item }} state=installedwith_items:- httpd- memcachedtags:- packages- template: src=templates/src.j2 dest=/etc/foo.conftags:- configuration### 执行playbook可以指定只执行标签对应任务或跳过标签对应任务 # ansible-playbook example.yml --tags "configuration,packages" # ansible-playbook example.yml --skip-tags "notification"14、failed_when 用来控制playbook退出- name: Check if firewalld is installedcommand: rpm -q firewalldregister: firewalld_checkfailed_when: firewalld_check.rc > 1when: ansible_os_family == 'RedHat' 15、pre_tasks/post_tasks 用来设置在执行roles模块之前和之后需要执行的任务16、wait_for 等待一个端口变得可用或者等待一个文件变得可用- local_action: wait_for port=22 host="{{ ansible_ssh_host | default(inventory_hostname) }}" search_regex=OpenSSH delay=10 #等待openssh启动,10s检查一次- name: Wait for container sshwait_for:port: "22"delay: "{{ ssh_delay }}"search_regex: "OpenSSH"host: "{{ ansible_host }}"delegate_to: "{{ physical_host }}"register: ssh_wait_checkuntil: ssh_wait_check | successretries: 3when:- (_mc is defined and _mc | changed) or (_ec is defined and _ec | changed)- not is_metal | booltags:- common-lxc17、执行shell命令 ### ignore_errors为true表示命令执行出错也不会退出playbook - name: Check if clean is neededcommand: docker exec openvswitch_vswitchd ovs-vsctl br-exists br-tunregister: resultignore_errors: True18、切换用户 ### 使用become会先切换成apache用户,再执行command命令,默认become_user用户为root ### (如果你ansible配置的就是root用户的免密码登入那就不需要become了) - name: Run a command as the apache usercommand: somecommandbecome: truebecome_user: apache 检测链表是否为空### pip_wheel_install为链表变量 - name: Install wheel packagesshell: cd /tmp/wheels && pip install {{ item }}*with_items:- "{{ pip_wheel_install | default([]) }}"when: pip_wheel_install > 019、when中使用jinja2 when表达式中不建议直接使用{{}}的方式来获取变量值,如果变量是字符串可以使用管道操作| string来获取变量值- name: Checking free port for OVNvars:service: "{{ neutron_services[item.name] }}"wait_for:host: "{{ hostvars[inventory_hostname]['ansible_' + api_interface]['ipv4']['address'] }}"port: "{{ item.port }}"connect_timeout: 1state: stoppedwhen:- container_facts[ item.facts | string ] is not defined- service.enabled | bool- service.host_in_groups | boolwith_items:- { name: "ovn-nb-db-server", port: "{{ ovn_northdb_port }}", facts: "ovn_nb_db" }- { name: "ovn-sb-db-server", port: "{{ ovn_sourthdb_port }}", facts: "ovn_sb_db" }20、uri web访问,类似执行curl命令 uri模块主要用于发送HTTP协议,通过使用uri模块,可以让目标主机向指定的网站发送如Get、Post这样的HTTP请求,并且能得到返回的状态码。- name: test proxy URL for connectivityuri:url: "{{ repo_pkg_cache_url }}/acng-report.html"method: "HEAD"register: proxy_checkfailed_when: falsetags:- common-proxy21、local_action 将任务放在ansible控制主机(运行ansible-playbook的主机)上执行- name: Check if the git cache exists on deployment hostlocal_action:module: statpath: "{{ repo_build_git_cache }}"register: _local_git_cachewhen: repo_build_git_cache is defined22、When语句官方文档在有的时候play的结果依赖于变量、fact或者是前一个任务的执行结果,从而需要使用到条件语句。有的时候在特定的主机需要跳过特定的步骤,例如在安装包的时候,需要指定主机的操作系统类型,或者是当操作系统的硬盘满了之后,需要清空文件等在ansible中,我们可以使用如下比较运算符:== :比较两个对象是否相等,相等为真!= :比较两个对象是否不等,不等为真> :比较两个值的大小,如果左边的值大于右边的值,则为真< :比较两个值的大小,如果左边的值小于右边的值,则为真>= :比较两个值的大小,如果左边的值大于右边的值或左右相等,则为真<= :比较两个值的大小,如果左边的值小于右边的值或左右相等,则为真逻辑运算符:and :逻辑与,当左边与右边同时为真,则返回真or :逻辑或,当左边与右边有任意一个为真,则返回真not :取反,对一个操作体取反( ) :组合,将一组操作体包装在一起,形成一个较大的操作体判断变量defined :判断变量是否已经定义,已经定义则返回真undefind :判断变量是否已经定义,未定义则返回真none :判断变量值是否为空,如果变量已经定义,但是变量值为空,则返回真判断执行结果success 或 succeeded:通过任务的返回信息判断任务的执行状态,任务执行成功则返回真failure 或 failed:通过任务的返回信息判断任务的执行状态,任务执行失败则返回真change 或 changed:通过任务的返回信息判断任务的执行状态,任务执行状态为changed则返回真skip 或 skipped:通过任务的返回信息判断任务的执行状态,当任务没有满足条件,而被跳过执行时,则返回真判断路径的使用方式:file : 判断路径是否是一个文件,如果路径是一个文件则返回真directory :判断路径是否是一个目录,如果路径是一个目录则返回真link :判断路径是否是一个软链接,如果路径是一个软链接则返回真mount:判断路径是否是一个挂载点,如果路径是一个挂载点则返回真exists:判断路径是否存在,如果路径存在则返回真在2.6及以后的版本,支持直接写下面的关键字;2.5之前的版本需要在前面加 is 或 is not 判断字符串:lower:判断包含字母的字符串中的字母是否是纯小写,字符串中的字母全部为小写则返回真upper:判断包含字母的字符串中的字母是否是纯大写,字符串中的字母全部为大写则返回真判断整除even :判断数值是否是偶数,是偶数则返回真odd :判断数值是否是奇数,是奇数则返回真divisibleby(num) :判断是否可以整除指定的数值,如果除以指定的值以后余数为0,则返回真subset:判断一个list是不是另一个list的子集,是另一个list的子集时返回真superset: 判断一个list是不是另一个list的父集,是另一个list的父集时返回真string:判断对象是否是一个字符串,是字符串则返回真number:判断对象是否是一个数字,是数字则返回真下面的例子表示为使用when语句,如下:tasks:- name: "shutdown Debian flavored systems"command: /sbin/shutdown -t nowwhen: ansible_os_family == "Debian"也可以使用括号来表示一组条件,如下所示:tasks:- name: "shutdownCentOS6andDebian7systems"command: /sbin/shutdown -t nowwhen: (ansible_distribution == "CentOS" and ansible_distribution_major_version == "6") or(ansible_distribution == "Debian" and ansible_distribution_major_version == "7")假设需要忽略一个语句的错误,根据执行的结果是成功还是失败从而执行不同的命令,如下(使用的是jinja2的过滤):tasks:- command: /bin/false 没有 - name 时,此行将被默认成为标题-- TASK: [command: /bin/false] register: resultignore_errors: True- command: /bin/somethingwhen: result|failed- command: /bin/something_elsewhen: result|succeeded- command: /bin/still/something_elsewhen: result|skipped当接收到一个变量是一个字符串的时候,然后想做一个数字的比较,那么可以使用如下的方式(在这个例子中远程主机上需要有lsb_package包):tasks:- shell: echo "only on Red Hat 6, derivatives, and later"when: ansible_os_family == "RedHat" and ansible_lsb.major_release|int >= 6在playbooks中或者inventory清单中定义的变量也是可以使用,假设任务的执行依赖于一个布尔变量,如下:vars:epic: true条件执行如下所示:tasks:- shell: echo "This certainly is epic!"when: epic或者使用如下形式:tasks:- shell: echo "This certainly isn't epic!"when: not epic如果需要的变量没有定义,那么可以skip或者使用jinja2的defined如下所示:tasks:- shell: echo "I've got '{{ foo }}' and am not afraid to use it!"when: foo is defined- fail: msg="Bailing out. this play requires 'bar'"when: bar is undefined当结合使用when和with_items的时候,需要注意的是when语句会对每个item进行单独的处理,如下所示:tasks:- command: echo {{ item }}with_items: [ 0,2,4,6,8,10 ]when: item > 53、在roles中和include中使用when当几个任务都是使用相同的条件的时候,那么可以将条件写在include之中,那么当写在include的时候,每个任务都会去判断条件,如下所示:- include: tasks/sometasks.ymlwhen: "'reticulatingsplines'inoutput"或者在roles中使用,如下:- hosts: webserversroles:- { role:debian_stock_config,when:ansible_os_family == 'Debian' }4、条件导入在playbook中可能会根据一些特定的标准从而做不同的事情,在一个playbook中工作在不同的平台和os版本是最好的例子如下的例子表示,在centos和debian中apache的包是不同的,从而可以使用以下:---- hosts: allremote_user: rootvars_files:- "vars/common.yml"- [ "vars/{{ansible_os_family}}.yml","vars/os_defaults.yml" ]tasks:- name: make sure apache is runningservice: name={{ apache }} state=running另外,在变量文件中只包含key和values,如下:---# for vars/CentOS.yml apache: httpdsomethingelse: 42如何工作的呢?当操作系统为centos的时候,那么会加载变量/vars/centos.yml,当文件不存在的时候,那么会加载defaults.yml,当没有找到任何文件的时候,那么就会出错。当操作系统为debian的时候,那么就会加载变量/vars/debian.yml,没有就加载defaults.yml当使用整个功能的时候,在运行playbook之前必须先安装facter或者ohai,也可以直接在playbook中使用如下所示:# for facteransible -m yum -a "pkg=facter state=present"ansible -m yum -a "pkg=ruby-json state=present"# for ohaiansible -m yum -a "pkg=ohai state=present"5、基于变量选择文件和模板在有的时候,配置文件使用copy或者是template的时候,可能会依赖于变量。下面的例子中表示使用template输出一个配置文件,在centos和debian中不同,如下:- name: template a filetemplate: src={{ item }} dest=/etc/myapp/foo.confwith_first_found:- files:- {{ ansible_distribution }}.conf- default.confpaths:- search_location_one/somedir/- /opt/other_location/somedir/6、注册变量在playbook中可以使用变量的值便于其他的任务用到。关键字register用来保存变量值,整个变量可以使用在template中,动作行中,或者是when语句中,如下所示:- name: test playhosts: alltasks:- shell: cat /etc/motdregister: motd_contents- shell: echo "motd contains the word hi"when: motd_contents.stdout.find('hi') != -1注册的变量值可以用stdout得到,或者用with_items得到,也可以使用stdout_lines得到,如下所示:- name: registered variable usage as a with_items listhosts: alltasks:- name: retrieve the list of home directoriescommand: ls /homeregister: home_dirs- name: add home dirs to the backup spoolerfile: path=/mnt/bkspool/{{ item }} src=/home/{{ item }} state=linkwith_items: home_dirs.stdout_lines# same as with_items: home_dirs.stdout.split()23、文件组装模块——assemble(主要用于把多份配置文件片段组装成一个配置文件) Ø 将/root/demo下的片段组装后放到/root/target目录下 ansible all –m assemble –a “dest=/root/demo src=/root/target”24、文件拉取模块——fetch Ø 将远端主机的/etc/salt/minion文件收集回服务器/root/demo目录下 ansible all –m fetch –a “dest=/root/demo src=/etc/salt/minion “25、文件管理模块——filefile 如果是directory,那么则会创建文件夹link 如果是file,则会创建文件 state 默认值:file directory 如果是link,则会创建链接hard 如果是hard,则会创建硬链接touch 如果是touch,则会创建文件absent 如果是absent,则会删除文件26、unarchive模块 用于解压文件,模块包含如下选项:copy:在解压文件之前,是否先将文件复制到远程主机,默认为yes。若为no,则要求目标主机上压缩包必须存在。creates:指定一个文件名,当该文件存在时,则解压指令不执行dest:远程主机上的一个路径,即文件解压的路径 grop:解压后的目录或文件的属组list_files:如果为yes,则会列出压缩包里的文件,默认为no,2.0版本新增的选项mode:解决后文件的权限src:如果copy为yes,则需要指定压缩文件的源路径 owner:解压后文件或目录的属主 示例如下:- unarchive: src=foo.tgz dest=/var/lib/foo- unarchive: src=/tmp/foo.zip dest=/usr/local/bin copy=no- unarchive: src=https://example.com/example.zip dest=/usr/local/bin copy=no27、fail 用于终止当前playbook的执行,通常与条件语句组合使用,当满足条件时,终止当前play的运行。可以直接由failed_when取代。 'changed_when'关键字的作用是在条件成立时,将对应任务的执行状态设置为changed 选项只有一个: msg:终止前打印出信息 示例: - fail: msg="The system may not be provisioned according to the CMDB status."when: cmdb_status != "to-be-staged"28、pause 在playbook执行的过程中暂停一定时间或者提示用户进行某些操作 常用参数: minutes:暂停多少分钟 seconds:暂停多少秒 prompt:打印一串信息提示用户操作 示例: - name: wait on user inputpause: prompt="Warning! Detected slight issue. ENTER to continue CTRL-C a to quit" - name: timed waitpause: seconds=3029、关于 async 和 poll 有的任务执行起来却不那么直接,可能会花比较长的时间,甚至可能会比ssh的超时时间还要长。这种情况任务是不是没法执行了? ansible考虑到了这种情况,官方文档介绍了这个问题的解决方法,就是让下发的任务执行的连接变为异步:任务下发之后,长连接不再保持,而是每隔一段时间轮询结果,直到任务结束。 他们在playbook的任务中加入两个参数:async和poll async参数值代表了这个任务执行时间的上限值。即任务执行所用时间如果超出这个时间,则认为任务失败。此参数若未设置,则为同步执行。 poll参数值代表了任务异步执行时轮询的时间间隔。如果poll为0,就相当于一个不关心结果的任务。官方给出例子:----hosts: allremote_user: roottasks:- name: simulate long running op (15 sec), wait for up to 45 sec, poll every 5 seccommand: /bin/sleep 15async: 45poll: 5如果还想要更方便地看轮询结果,ansible还提供了这个模块async_status。---# Requires ansible 1.8+- name: 'YUM - fire and forget task'yum: name=docker-io state=installedasync: 1000poll: 0register: yum_sleeper- name: 'YUM - check on fire and forget task'async_status: jid={{ yum_sleeper.ansible_job_id }}register: job_resultuntil: job_result.finishedretries: 30 第一个job执行异步任务,并且注册了一个名字叫yum_sleeper,用于提供给第二个job作为轮询对象,并且poll设为0,它自己不再轮询。 第二个job使用async_status模块,进行轮询并返回轮询结果。准备检查30次。结果如下:PLAY [all] *********************************************************************TASK [setup] ******************************************************************* ok: [cloudlab001]TASK [YUM - fire and forget task] ********************************************** ok: [cloudlab001]TASK [YUM - check on fire and forget task] ************************************* FAILED - RETRYING: TASK: YUM - check on fire and forget task (29 retries left). FAILED - RETRYING: TASK: YUM - check on fire and forget task (28 retries left). FAILED - RETRYING: TASK: YUM - check on fire and forget task (27 retries left). FAILED - RETRYING: TASK: YUM - check on fire and forget task (26 retries left). FAILED - RETRYING: TASK: YUM - check on fire and forget task (25 retries left). FAILED - RETRYING: TASK: YUM - check on fire and forget task (24 retries left). changed: [cloudlab001]PLAY RECAP ********************************************************************* cloudlab001 : ok=3 changed=1 unreachable=0 failed=0--- - hosts: allgather_facts: notasks:- shell: "ls /opt"register: returnvalue- debug:var: returnvalue30、debug 调试模块,用于在调试中输出信息 常用参数: msg:调试输出的消息,不能与var同时使用 var:将某个任务执行的输出作为变量传递给debug模块,debug会直接将其打印输出,不能与msg同时使用 verbosity:debug的级别(默认是0级,全部显示,如果设置为3时,会在 -vvv 时打印出信息)31、内置变量 groups 配置文件 justtest 如下:10.1.1.60 justtest.zsythink.net ansible_host=10.1.1.70 test71 anisble_host=10.1.1.71[testA] test60 ansible_host=10.1.1.60 test61 ansible_host=10.1.1.61[testB] justtest ansible_host=10.1.1.70[test:children] testA testB 上述清单中,显式的指定了三个组,testA组、testB组、test组,其中,testA组与testB组是test组的子组,除了组中的主机,还有三台主机没有任何分组,直接写在了清单中。 现在,我们获取一下groups变量的值,看看会返回哪些信息,随便操作清单中的任意一台主机即可,示例如下 # ansible justtest -m debug -a "msg={{groups}}" justtest | SUCCESS => {"changed": false, "msg": {"all": ["10.1.1.60", "justtest.zsythink.net", "test71", "test60", "test61", "justtest"], "test": ["test60", "test61", "justtest"], "testA": ["test60", "test61"], "testB": ["justtest"], "ungrouped": ["10.1.1.60", "justtest.zsythink.net", "test71"]} } 从上述返回信息可以看出,所有主机默认被分成了组名为"all"的组,testA组中有两台主机,testB组中有一台主机,由于testA组和testB组都属于test组的子组,所以testA组与testB组中的主机都属于test组,由于有三台主机在清单中并未分组,所以,ansible自动将没有分组的主机分到了名为"ungrouped"的组中,即组名为"未分组"的组。 我们还能够通过组名,获取到指定组的分组信息,假设,我想要获取到上例中test组中的主机名称,则可以使用如下方法。 # ansible justtest -m debug -a "msg={{groups.test}}" # ansible justtest -m debug -a "msg={{groups['test']}}" # ansible justtest -m debug -a "msg={{groups.ungrouped}}"32、handlers模块 之 meta模块 --- - hosts: justtestremote_user: roottasks:- name: task1file: path=/testdir/testfilestate=touchnotify: handler1- name: task2file: path=/testdir/testfile2state=touchnotify: handler2- meta: flush_handlers- name: task3file: path=/testdir/testfile3state=touchnotify: handler3handlers:- name: handler1file: path=/testdir/ht1state=touch- name: handler2file: path=/testdir/ht2state=touch- name: handler3file: path=/testdir/ht3state=touch 如上例所示,我在task1与task2之后写入了一个任务,我并没有为这个任务指定name属性,这个任务使用meta模块,meta任务是一种特殊的任务,meta任务可以影响ansible的内部运行方式,上例中,meta任务的参数值为flush_handlers, "meta: flush_handlers"表示立即执行之前的task所对应handler,什么意思呢? 意思就是,在当前meta任务之前,一共有两个任务,task1与task2,它们都有对应的handler,当执行完task1与task2以后,立即执行对应的handler,而不是像默认情况那样在所有任务都执行完毕以后才能执行各个handler,那么我们来实际运行一下上述剧本,运行结果如下33、handlers模块 之 立即执行 我们还可以在一个task中一次性notify多个handler,怎样才能一次性notify多个handler呢? 你可能会尝试将多个handler使用相同的name,但是这样并不可行,因为当多个handler的name相同时,只有一个handler会被执行。 所以,我们并不能通过这种方式notify多个handler,如果想要一次notify多个handler,则需要借助另一个关键字,它就是'listen'。 你可以把listen理解成"组名",我们可以把多个handler分成"组",当我们需要一次性notify多个handler时,只要将多个handler分为"一组",使用相同的"组名"即可,当notify对应的值为"组名"时,"组"内的所有handler都会被notify,这样说可能还是不容易理解,我们来看个小示例,示例如下 --- - hosts: justtestremote_user: roottasks:- name: task1file: path=/testdir/testfilestate=touchnotify: handler group1handlers:- name: handler1listen: handler group1file: path=/testdir/ht1state=touch- name: handler2listen: handler group1file: path=/testdir/ht2state=touch34、tags模块 --- - hosts: justtestremote_user: roottags: httpdtasks:- name: install httpd packagetags: ['package']yum:name=httpdstate=latest- name: start up httpd servicetags:- service- always # 意思是service的tag总会被执行 service:name: httpdstate: started 当tags写在play中而非task中时,play中的所有task会继承当前play中的tags,而上例中,两个任务都会继承httpd标签,同时还有拥有自己的标签。ansible-playbook --tags package,service testhttpd.yml 或者 ansible-playbook --tags httpd testhttpd.yml 执行的结果是一样的,都会将两个标签进行执行 其实,ansible还预置了5个特殊tag,这5个特殊tag分别为: always never(2.5后的版本才有) tagged untagged allalways:当我们把任务的tags的值指定为always时,那么这个任务就总是会被执行,除非你使用'--skip-tags'选项明确指定不执行对应的任务, --skip-tags always 如果存在多个tag标记了 always,我们只想跳过某一个, 那么可以使用 --skip-tags servicenever:在2.5版本的ansible中,引入了新的特殊标签 'never', 从字面上理解,never的作用应该与always正好相反,由于我当前使用的ansible版本为2.4(还没有引入never标签),所以当指定任务的标签为never时,貌似被ansible当做了自定义标签,所以如果你安装了2.5版本的ansible,可以尝试一下never标签的作用,由于还没有实际使用过2.5版本,所以此处暂时不进行示例。ansible-playbook --tags tagged testtag.yml 上述命令表示只执行有标签的任务,没有任何标签的任务不会被执行。ansible-playbook --skip-tags tagged testtag.yml 上述命令表示跳过包含标签的任务,即使对应的任务包含always标签,也会被跳过。ansible-playbook --tags untagged testtag.yml 上述命令表示只执行没有标签的任务,但是如果某些任务包含always标签,那么这些任务也会被执行。特殊标签all表示所有任务会被执行,不用指定,默认情况下就是使用这个标签。35、变量-vars 使用 vars 可以在当前的play中设置变量 --- - hosts: allvars:v: wawotasks:- name invoke vfile:path: /home/{{ v }}state: touch也可以定义属性: --- - hosts: allvars:nginx:conf80: /etc/nginx/conf.d/80.confconf8080: /etc/nginx/conf.d/8080.conftasks:- name invoke vfile:path: "{{ nginx.conf80 }}" == "{{ nginx['conf80'] }}" 两个方式等价state: touch 此处的变量增加了 " 引号,原因是使用变量是出于开头的位置。 在playbook中参数赋值,可以使用 : 也可以使用 = 。当使用 = 进行赋值时,就不需要考虑使用 " 引号了。 但是要使用 : 冒号时,就需要在紧邻参数的那个变量处添加 " 引号。path={{ nginx.conf80 }}path: /home/{{ v }} 在实际使用中,我们提倡"变量文件分离",可以通过 vars_files 关键字引入文件vars_files:- /testdir/ansible/nginx_vars.yml- yaml_file_path- include_vars:file: /testdir/ansible/testfilename: get_var 将文件中的所有变量赋值给get_varextensions: [yaml,yml,json,varfile] 指定包含的文件depth: 1 指定目录深度files_matching: "^var_.*" 指定过滤条件ignore_files: ["^var_.*",varintest.yaml] 忽略过滤条件36、变量-注册变量 register --- - hosts: justtestremote_user: roottasks:- name: test shellshell: "echo test > /var/testshellfile"register: testvar- name: shell module return valuesdebug:var: testvar testvar会将shell执行的结果进行保存 37、提示用户操作-交互操作 --- - hosts: 192.168.43.130vars_prompt:- name: "your_name"prompt: "What is your name"private: no # 可以让输入可见,不加此属性看不到对应的输入信息,适用于密码- name: "your_age"prompt: "How old are you"tasks:- name: output varsdebug:msg: Your name is {{your_name}},You are {{your_age}} years old. 还可以使用选择的形式的如下:vars_prompt:- name: "solution"prompt: "Choose the solution you want \n A: solutionA\nB: solutionB\nC: solutionC\n" private: nodefault: A 38、外部设置变量-通过 -e 和 --extra-vars 通过对应的文件设置变量,调用时,需要使用 @file_path 进行引用 ansible-playbook cmdvar.yml -e "@/testdir/ansible/testvar.yml"39、set_fact 定义变量 --- - hosts: justtestremote_user: roottasks:- set_fact:testvar: "testtest"- debug:msg: "{{testvar}}"40、循环 with_list、with_items、with_flattened、with_together、with_cartesian --- - hosts: justtestremote_user: rootgather_facts: novars:dirs:- "/opt/a"- "/opt/b"- "/opt/c"- "/opt/d"tasks:- file:path: "{{item}}"state: touchwith_items: "{{dirs}}"--- - hosts: justtestremote_user: rootgather_facts: notasks:- debug:msg: "{{item}}"with_items: [ 1, 2, 3 ]||with_items:- 1- 2- 3 with_list 经过with_list处理后,每个嵌套在大列表中的小列表都被当做一个整体存放在item变量中,最终被debug作为一个小整体输出了,而不会像with_items一样将小列表"展开拉平"后一并将小列表中的元素循环输出。 with_flattened 拉平展开,与with_list基本一致 with_together with_together可以将两个列表中的元素"对齐合并",单单用语言来描述,不是特别容易理解,不如来看一个小示例,示例playbook如下:--- - hosts: justtestremote_user: rootgather_facts: notasks:- debug:msg: "{{ item }}"with_together:- [ 1, 2, 3 ]- [ a, b, c ] 结果: TASK [debug] ****************************** ok: [justtest] => (item=[1, u'a']) => {"changed": false,"item": [1,"a"],"msg": [1,"a"] } ok: [justtest] => (item=[2, u'b']) => {"changed": false,"item": [2,"b"],"msg": [2,"b"] } ok: [justtest] => (item=[3, u'c']) => {"changed": false,"item": [3,"c"],"msg": [3,"c"] } with_cartesian 是将两个列表进行笛卡尔积组合 与 with_nested 使用一致 --- - hosts: justtestremote_user: rootgather_facts: notasks:- file:state: directorypath: "/testdir/testdir/{{ item.0 }}/{{ item.1 }}"with_cartesian:- [ a, b, c ]- [ test1, test2 ]"with_indexed_items"应该与"索引"有关,没错,"with_indexed_items"的作用就是在循环处理列表时为列表中的每一项添加"数字索引","索引"从0开始with_sequence start=1 end=5 stride=1 从1到5,每次增加1 --- - hosts: justtestremote_user: rootgather_facts: notasks:- debug:msg: "{{ item }}"with_sequence: start=1 end=5 stride=1with_file 获取主机文件的内容的41、有几种分隔符。默认的Jinja分隔符配置如下: {% ... %} 对于声明 可以直接将 for等语句放到里面执行 {{ ... }} 对于表达式打印到模板输出 {# ... #} for Comments不包含在模板输出中 # ... ## 对于行语句42、过滤器 变量可以通过过滤器修改。过滤器通过管道符号(|)与变量分隔,并且在括号中可以包含可选参数。可以链接多个过滤器。一个过滤器的输出应用于下一个过滤器。 abs 绝对值 capitalize 首字母大写 center 将值集中在给定宽度的字段中 default 设置默认值 first 返回序列中第一项 float 转化成浮点型的数据 join {{ [1, 2, 3]|join('|') }} -> 1|2|3 {{ [1, 2, 3]|join }} -> 123 last 返回序列中最后一项 length == count 返回长度 max {{ [1, 2, 3]|max }} min {{ [1, 2, 3]|min }} pprint 优雅打印 replace(s, old, new, count=None) {{ "Hello World"|replace("Hello", "Goodbye") }} reverse 反向打印 round round(value,precision = 0,method ='common' )将数字四舍五入到给定的精度。第一个参数指定精度(默认为0),第二个参数指定舍入方法:'common' 向上或向下舍入'ceil' 总是围捕'floor' 总是四舍五入 safe(值) 将值标记为安全,这意味着在启用了自动转义的环境中,此变量不会被转义。 sort 对可迭代进行排序。默认情况下,它会按升序排序,如果您将其作为第一个参数传递,它将反转排序。 string 如果尚未创建字符串unicode。这样,标记字符串不会转换回unicode。 striptags(值) 剥离SGML / XML标记并将相邻的空格替换为一个空格。 sum 返回数字序列的总和加上参数'start'的值(默认为0)。当序列为空时,它返回start。 title 返回值的标题版本。即单词将以大写字母开头,所有剩余字符均为小写。 trim #将字符串开头和结尾的空格去除 truncate 返回字符串的截断副本。 unique 去重 upper #将字符串转换成纯大写 wordcount 计算该字符串中的单词。--- - hosts: justtestremote_user: rootvars:testvar: "abc123ABC 666"testvar1: " abc "testvar2: '123456789'testvar3: "1a2b,@#$%^&"tasks:- debug:#将字符串转换成纯大写msg: "{{ testvar | upper }}"- debug:#将字符串转换成纯小写msg: "{{ testvar | lower }}"- debug:#将字符串变成首字母大写,之后所有字母纯小写msg: "{{ testvar | capitalize }}"- debug:#将字符串反转msg: "{{ testvar | reverse }}"- debug:#返回字符串的第一个字符msg: "{{ testvar | first }}"- debug:#返回字符串的最后一个字符msg: "{{ testvar | last }}"- debug:#将字符串开头和结尾的空格去除msg: "{{ testvar1 | trim }}"- debug:#将字符串放在中间,并且设置字符串的长度为30,字符串两边用空格补齐30位长msg: "{{ testvar1 | center(width=30) }}"- debug:#返回字符串长度,length与count等效,可以写为countmsg: "{{ testvar2 | length }}"- debug:#将字符串转换成列表,每个字符作为一个元素msg: "{{ testvar3 | list }}"- debug:#将字符串转换成列表,每个字符作为一个元素,并且随机打乱顺序#shuffle的字面意思为洗牌msg: "{{ testvar3 | shuffle }}"- debug:#将字符串转换成列表,每个字符作为一个元素,并且随机打乱顺序#在随机打乱顺序时,将ansible_date_time.epoch的值设置为随机种子#也可以使用其他值作为随机种子,ansible_date_time.epoch是facts信息msg: "{{ testvar3 | shuffle(seed=(ansible_date_time.epoch)) }}"43、lookup 模块 从2.5版本开始,官方加入了 loop 关键字进行循环操作,来代替 with_xxx 的风格 而实际上内部的操作,就是 loop + lookup 的操作在说循环和lookup插件之间的关系,需要注意,不要错误的以为lookup插件只能实现循环操作, lookup插件有很多,有的lookup插件与"循环操作"完全没有关系, lookup类型的插件的主要作用是访问外部的数据源, 比如,获取到外部数据并赋值给某个变量,以便之后使用这些数据,lookup插件的操作都是在ansible主机中进行的,与目标主机没有关系。以"with_"开头的循环实际上就是"with_"和"lookup()"的组合,lookup插件可以作为循环的数据源,通过以上描述,应该已经明白了我们之前总结的循环与各种lookup插件之间的关系了吧。--- - hosts: justtestremote_user: rootgather_facts: notasks:- debug:msg: "{{ lookup('file','/testdir/testfile') }}" 获得/testdir/testfile文件的内容- debug: 可以获得多个文件的内容msg: "{{ lookup('file','/testdir/testfile','/testdir/testfile1') }}"- debug: 对每个文件内容单独放在一个列表中 2.5 版本中添加的msg: "{{ lookup('file','/testdir/testfile','/testdir/testfile1',wantlist=true) }}"- debug: 2.6版本中引入的错误判读msg: "{{ lookup('file','/testdir/testfile',errors='ignore') }}"#errors的值需要使用引号引起,errors的值可以设置为ignore、warn或者strict,缺省值为strict 通过ansible-doc -t lookup -l命令查看到如下列表, 我们可以通过使用lookup + 下面的列表信息进行组合使用。 cartesian 返回列表的笛卡尔积 chef_databag 从Chef的databag获取数据 consul_kv 从consul键值存储中获取元数据。 credstash 在AWS上检索信用卡的秘密 csvfile 从TSV或CSV文件读取数据 cyberarkpassword 从CyberArk AIM获得秘密 dict 从字典返回键/值对项 dig 使用DNSPython库查询DNS dnstxt 查询域的DNS TXT字段 env 读取环境变量的值 etcd 从ETCD服务器获取信息 file 读取文件内容 fileglob 与模式匹配的列表文件 filetree 递归地匹配目录树中的所有文件 first_found 返回从列表中找到的第一个文件 flattened 返回完全展开的单个列表 hashi_vault 从HasiHCORP拱顶中找回秘密 hiera 从HIELA数据获取信息 indexed_items 重写列表以返回“索引项目” ini 从INI文件读取数据 inventory_hostnames 与主机模式匹配的库存主机列表 items 项目清单 keyring 从操作系统钥匙抓取秘密 lastpass 从LASTPASS获取数据 lines 从命令读取行 list 简单地返回它所给予的。 mongodb 从MangGDB查找信息 nested 用其他列表的嵌套元素编写列表 password 检索或生成随机密码,存储在文件中 passwordstore 使用passwordstore.org的通行工具管理密码 pipe 从命令读取输出 random_choice 从列表返回随机元素 redis_kv 从ReDIS获取数据 sequence 基于数字序列生成列表 shelvefile 从Python搁置文件读取密钥 subelements 从字典列表中遍历嵌套密钥 template 用Jinja2 检索模板后的文件内容 together 将列表合并为同步化列表 url 从URL返回内容44、block rescue always的使用 block 块,可以将多个任务整合到一起执行,可以添加条件判断 when 进行判断执行 --- - hosts: justtestremote_user: roottasks:- debug:msg: "task1 not in block"- block:- debug:msg: "task2 in block1"- debug:msg: "task3 in block1"when: 2 > 1 rescue 捕获 block 的执行失败任务(任意一个失败都会触发rescue) 执行block成功不会触发rescue --- 不使用 rescue --- - hosts: justtest - hosts: justtestremote_user: root remote_user: roottasks: tasks:- block: - shell: 'ls /ooo'- shell: 'ls /ooo' register: return_valuerescue: ignore_errors: true- debug: - debug:msg: 'I caught an error' msg: "I caught an error"when: return_value is failed我们能从中看到,使用这种方式的便捷性always 不管怎么样都要执行的一部分 说到这里是不是感觉到了,有一种 try{}catch{}finally{} 的感觉了?--- - hosts: justtestremote_user: roottasks:- block:- debug:msg: 'I execute normally'- command: /bin/false- debug:msg: 'I never execute, due to the above task failing'rescue:- debug:msg: 'I caught an error'- command: /bin/false- debug:msg: 'I also never execute'always:- debug:msg: "This always executes"44、关于使用Jinja2 template 模块 为什么要用到 Jinja2 主要是为了根据变量动态生成适合的模板 比如多机器下的模板,可以根据不同的机器设置变量来生成对应的模板,并推送到对应的机器上。 就是为了方便快捷。 Jinja2的语法是由 variables (变量)和 statement (语句)组成,如下: 1、variables:可以输出数据 ` my_variables ` {{ some_dudes_name | capitalize }} 可以使用过滤器进行变量使用 2、statements: 可以用来创建条件和循环等 if语句: {% if my_conditional %} 进行语句判断 ... {% endif %} for 语句: {% for item in all_items %} `item` …… {% endfor %}生成文件内容 # cat test.j2 # cat test jinja2 test jinja2 test {{ 3 + 2 }} 5 {{ 3 - 4 }} -1 {{ 3 * 5 }} 15 {{ 2 ** 3 }} 8 {{ 7 / 5 }} 1.4 {{ 7 // 5 }} 1 {{ 17 % 5 }} 2生成文件内容 # cat test.j2 # cat test jinja2 test jinja2 test {{ 1 in [1,2,3,4] }} True {{ 1 not in [1,2,3,4] }} False条件: {% if 条件一 %} ... {% elif 条件N %} ... {% else %} ... {% endif %}设置值: {% set teststr='abc' %} {{ teststr }}循环: {% for 迭代变量 in 可迭代对象 %} {{ 迭代变量 }} {% endfor %} # cat test.j2 jinja2 test {% for i in [3,1,7,8,2] %} {% for i in [3,1,7,8,2] -%} 在for结束前加 - {{ i }} {{ i }}{{ ' ' }} {% endfor %} {%- endfor %} endfor开始前加 - 可以避免换行# cat test.j2 # cat test.j2 jinja2 test jinja2 test {% for i in [3,1,7,8,2] -%} {% for key,val in {'name':'bob','age':18}.iteritems() %} {{ i~' ' }} ~ 就是字符串连接符 {{ key ~ ':' ~ val }} {%- endfor %} {% endfor %}loop.index 当前循环操作为整个循环的第几次循环,序号从1开始 loop.index0 当前循环操作为整个循环的第几次循环,序号从0开始 loop.revindex 当前循环操作距离整个循环结束还有几次,序号到1结束 loop.revindex0 当前循环操作距离整个循环结束还有几次,序号到0结束 loop.first 当操作可迭代对象中的第一个元素时,此变量的值为true loop.last 当操作可迭代对象中的最后一个元素时,此变量的值为true loop.length 可迭代对象的长度 loop.depth 当使用递归的循环时,当前迭代所在的递归中的层级,层级序号从1开始 loop.depth0 当使用递归的循环时,当前迭代所在的递归中的层级,层级序号从0开始 loop.cycle() 这是一个辅助函数,通过这个函数我们可以在指定的一些值中进行轮询取值,具体参考之后的示例{% for i in [7,1,5,3,9] if i>3 %} {% for i in [7,1,5,3,9] %} {{ i ~'----'~ loop.index }} {% if loop.index is even %} {% endfor %} {%continue%}{%endif%} {% for i in [7,1,5,3,9] %} {{ i ~'----'~ loop.index }} {% if i>3 %} {% endfor %} {{ i ~'----'~ loop.index}} {% endif %} {% endfor %}ansible all -m template -a "src=test.j2 dest=/opt/test"可以在结果:cat /opt/testjinja2 testFalseTrueabc31782
参考的网站较多,不一一列举了,但有一个重要的参考是下面的这位的。
http://www.zsythink.net/archives/category/%e8%bf%90%e7%bb%b4%e7%9b%b8%e5%85%b3/ansible/
===========================END===========================
转载于:https://www.cnblogs.com/wozijisun/p/9964926.html
Ansible学习实战手记-你想要知道的可能都在这里了相关推荐
- NLP机器翻译深度学习实战课程基础 | 深度应用
作者 | 小宋是呢 来源 | CSDN博客 0.前言 深度学习用的有一年多了,最近开始 NLP 自然处理方面的研发.刚好趁着这个机会写一系列 NLP 机器翻译深度学习实战课程. 本系列课程将从原理讲解 ...
- 深度学习实战_五天入门深度学习,这里有一份PyTorch实战课程
这是一门五天入门深度学习的实战课程. 想入门深度学习的小伙伴有福了!dataflowr 最近推出了一门五天初步掌握深度学习的实战教程(实战使用 PyTorch 框架),有知识点有实例有代码,值得一看. ...
- 【赠书】重磅好书联邦学习实战来袭!你值得拥有一本
我们以前给大家介绍过杨强教授团队所著的业界首本联邦学习的书籍,现在这本书的实战版来了,5月刚刚出版,本次给大家赠送3本新书,即<联邦学习实战>. 这是一本什么样的书 所谓联邦学习技术,是一 ...
- 实战手记:让百万级数据瞬间导入SQL Server
实战手记:让百万级数据瞬间导入SQL Server 想必每个DBA都喜欢挑战数据导入时间,用时越短工作效率越高,也充分的能够证明自己的实力.实际工作中有时候需要把大量数据导入数据库,然后用于各种程序计 ...
- 武汉学java_学习武汉Java开发想要找到工作,需要学到什么程度呢?
对于很多学习Java开发的人而言,学完后能收到一份高薪offer是他们的最终目标,那么,Java学到什么程度可以收到企业offer?下面,就给大家介绍一下. 一.基础要打牢 Java要学到什么程度才可 ...
- 『深度应用』NLP机器翻译深度学习实战课程·零(基础概念)
0.前言 深度学习用的有一年多了,最近开始NLP自然处理方面的研发.刚好趁着这个机会写一系列NLP机器翻译深度学习实战课程. 本系列课程将从原理讲解与数据处理深入到如何动手实践与应用部署,将包括以下内 ...
- 【阅读笔记】联邦学习实战——联邦个性化推荐案例
联邦学习实战--联邦个性化推荐案例 前言 1. 引言 2. 传统的集中式个性化推荐 2.1 矩阵分解 2.2 因子分解机 3. 联邦矩阵分解 3.1 算法详解 3.2 详细实现 4 联邦因子分解机 4 ...
- 深度学习实战-图像风格迁移
图像风格迁移 文章目录 图像风格迁移 简介 画风迁移 图像风格捕捉 图像风格迁移 图像风格内插 补充说明 简介 利用卷积神经网络实现图像风格的迁移. 画风迁移 简单来说就是将另一张图像的绘画风格在不改 ...
- Python爬虫学习实战
Python爬虫学习实战 前期回顾 概述 技术要求 实战 网页分析与数据提取 小说目录提取 小说章节内容 总结 前期回顾 Python爬虫学习之requests Python爬虫学习之数据提取(XPa ...
最新文章
- php自动断词,PHP自动分页、防止英文单词被截段、去除HTML代码
- 混凝土地坪机器人_创新引领 快速建造丨临时设施大项目部使用机器人等五项技术刷新建设新效率...
- deepin--更改最低亮度
- vscode 分支列表刷新_分钟将vscode撸成小霸王
- 在 JavaScript 中将 String 与 XML 相互转换
- Flowable官方指定中国社区成立了
- App个人信息保护管理暂行规定即将出台
- PC端页面如何调用QQ进行在线聊天?
- php 表情,PHP处理emoji表情
- 为什么美团股价大跌:疫情影响、阿里竞争与模式弊病
- Word秘籍:如何30秒做出精美的Word排版
- python绘制线段_绘制线条点图
- JavaScript:将输入的一串数字转换成中文大写,最高可写12位(千亿)
- 串灯控制盒去掉怎么接_彩灯控制器怎么接线
- 混合云是什么,主要有什么优缺点
- Games104 Lecture 7 游戏中渲染管线、后处理和其他的一切
- 常见电脑的屏幕比例和分辨率详谈
- ubuntu下 安装 wine QQ
- Linux Shell - 脚本中自动确认需要输入确认的命令
- LKDHelper使用LKDBHelper以实体类对象进行数据库的操作,例如新建一个新闻实体类,以这个类来
热门文章
- 第一个Hadoop程序——WordCount
- MYSQL数据库设计原则
- Java 接口(interface)的用途和好处
- 转:让 ThinkPad 的中键加小红帽模拟鼠标滚轮
- [转]Linux C语言头文件搜索路径
- 64位 unsigned char_Java位运算符详解
- Android检查是否自启动,android – 如何检查我的应用程序是否是默认启动器
- 【教程】Linux 下软 RAID 实现方案!!
- html手机端页面meta,手机页面的 HTMLmeta 标签使用与说明
- Knative 实战:基于 Knative Serverless 技术实现天气服务-下篇