序列(Sequence)是Python中的一种数据结构,这种数据结构根据索引来获取序列中的对象。
Python3中含有三种内建序列类型:list, tuple, string, range。其中range比较特殊,它是一个生成器,其他几个类型具有的一些序列特性对它并不适合,本篇不做详述。
切片操作就是对序列按照给定的索引和步长,截取序列中由连续对象组成的片段。
对于序列结构来说,索引和步长都有正负值,分别表示左右两个方向
索引的正方向从左往右取值,起始位置为0,有效范围为 [0, 序列长度-1]
索引的负方向从右往左取值,起始位置为-1,有效范围为 [-序列长度, -1]
因此任意一个序列结构数据的索引范围为-序列长度
到序列长度-1
范围内的连续整数。
用图来表示如下,对于一个字符串’Python’,它的正负索引如下1
2
3字符串 P y t h o n
正索引 0 1 2 3 4 5
负索引 -6 -5 -4 -3 -2 -1
理解了序列索引的特点就能很轻松对字符串进行切片。
1 | Sequence [start_index: end_index: step] |
*注意:
切片的结果不包含结束索引,即不包含最后的一位,-1代表列表的最后一个位置索引
写得再生动还不如一个例子有用1
2
3
4
5
6#生成一组列表
L = list(range(21))
>>> L
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
>>> len(L)
21
索引范围为:-21~20
切片操作 | 含义 | 结果 |
---|---|---|
L[0] | 取第一个元素 | 0 |
L[:] | 取一个完整的序列,相当于浅拷贝 | |
L[-1] | 取最后一个元素 | 20 |
L[1:5] | 截取从索引为1,到索引为5前面的元素 | [1, 2, 3, 4] |
L[-5:] | 截取后面5个元素 | [16, 17, 18, 19, 20] |
L[:10:2] | 开始到索引为10的元素(但不包括),每隔5个取一个元素 | [0, 5] |
L[::5] | 所有元素,每5个取一个 | [0, 5, 10, 15, 20] |
L[1::5] | 从索引为1的元素开始到结束,隔5个取一个元素 | [1, 6, 11, 16] |
L[::-1] | 将列表倒序 | |
L[::-4] | 倒序列表后按照step的绝对值间隔取元素 | [20, 16, 12, 8, 4, 0] |
L[::-3] | 同上 | [20, 17, 14, 11, 8, 5, 2] |
L[5:0:-1] | step为负数,逆向访问,切片访问到的正值索引范围为[1:5] | [5, 4, 3, 2, 1] |
L[1:6:-1] | step为负数,逆向访问,切片访问到的正值索引范围为[5:1],但其中无元素 | [] |
Ansible是一个简单的IT自动化管理系统,它能够实现自动化配置管理、应用程序部署、云服务管理、持续交付等功能,它是基于Python的paramiko模块实现的,使用ssh协议与Client通信,因此不需要在Client安装Agent。
Ansible有三个最吸引人的特点:
快速安装1
2
3sudo esay_install ansible
#或者
sudo pip install ansible
源码安装1
2
3
4
5
6
7
8
9yum install python-docutils python2-devel python-paramiko python2-cryptography #安装依赖
git clone git://github.com/ansible/ansible.git
cd ./ansible
make rpm
rpm -Uvh ./rpm-build/ansible-*.noarch.rpm
mkdir -pv /etc/ansible
cp examples/{ansible.cfg,hosts} /etc/ansible
1 | 语法:ansible <host-pattern> [-f forks] [-m module_name] [-a args] [options] |
ansible执行的时候会按照以下顺序查找配置项:1
2
3
4ANSIBLE_CONFIG (环境变量)
ansible.cfg (当前目录下)
.ansible.cfg (用户家目录下)
/etc/ansible/ansible.cfg
配置文件解析1
2
3
4
5
6
7
8
9
10
11
12
13[defaults]
action_plugins=/usr/share/ansible_plugins/action_plugins #用于加载一些外部插件
ansible_managed=Ansible managed: {file} modified on %Y-%m-%d %H:%M:%S by {uid} on {host} #可以在模板中插入{{ ansible_managed }}来表示执行
host_key_checking = False #当主机不在known_hosts时是否提示
ask_pass=False #控制Ansible playbook执行时是否会自动弹出询问密码,使用SSH 密钥匙做身份认证时需要关闭
ask_sudo_pass=False #控制Ansible playbook在执行sudo之前是否询问sudo密码
executable = /bin/bash
_forks=5
remote_port=22 #设置默认远程SSH端口号,不指定为22
remote_user = root #使用ansible playbook执行的默认用户名,不指定默认使用当前用户名称:
roles_path = /opt/mysite/roles #路径指的是’roles/’下的额外目录,用于playbook搜索Ansible roles,多个路径可以用冒号分隔。
Ansible可同时操作属于一个组的多台主机,组和主机之间的关系通过Inventory 文件配置.
默认为/etc/ansible/hosts1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26#配置文件格式
mail.jaydenz.org
[webservers] #设置组名
web1.jaydenz.org:8080 #可指明端口号
web2.jaydenz.org
web[3:10].jaydenz.org
[dbservers]
db1.jaydenz.org ansible_connection=ssh ansible_ssh_user=jaydenz #对于单个链接可以设置连接类型和用户名
db2.jaydenz.org
#通过jumper关键字设置别名
jumper ansible_ssh_port=5555 ansible_ssh_host=192.168.1.50 #会连接 192.168.1.50:5555
#其他参数说明
ansible_ssh_host #将要连接的远程主机名.与你想要设定的主机的别名不同的话,可通过此变量设置.
ansible_ssh_port #ssh端口号.如果不是默认的端口号,通过此变量设置.
ansible_ssh_user #默认的 ssh 用户名
ansible_ssh_pass #ssh密码(这种方式并不安全,我们强烈建议使用 --ask-pass 或 SSH 密钥)
ansible_sudo_pass #sudo密码(这种方式并不安全,我们强烈建议使用 --ask-sudo-pass)
ansible_sudo_exe #sudo命令路径(适用于1.8及以上版本)
ansible_connection #与主机的连接类型.比如:local, ssh 或者 paramiko. Ansible 1.2 以前默认使用 paramiko.1.2 以后默认使用 'smart','smart' 方式会根据是否支持 ControlPersist, 来判断'ssh' 方式是否可行.
ansible_ssh_private_key_file #ssh 使用的私钥文件.适用于有多个密钥,而你不想使用 SSH 代理的情况.
ansible_shell_type #目标系统的shell类型.默认情况下,命令的执行使用 'sh' 语法,可设置为 'csh' 或 'fish'.
ansible_python_interpreter #目标主机的 python 路径.适用于的情况: 系统中有多个 Python, 或者命令路径不是"/usr/bin/python",比如 \*BSD, 或者 /usr/bin/python;不是 2.X 版本的 Python.我们不使用 "/usr/bin/env" 机制,因为这要求远程用户的路径设置正确,且要求 "python" 可执行程序名不可为 python以外的名字(实际有可能名为python26);与 ansible_python_interpreter 的工作方式相同,可设定如 ruby 或 perl 的路径....
Ansible支持使用其他软件系统保存Inventory配置信息
1 | ansible-doc [options] [modules] #查看模块信息 |
ping:最常用的测试一个节点有没有配置好ssh连接的module,如果可以通过ansible成功连接,那么返回pong,使用时不需要传入参数
1 | ansible servers -m ping |
debug:用于调试的module,只是简单打印一些消息,有点像linux的echo命令。
通过参数msg定义打印的字符串,msg中可以嵌入变量,下面的例子中注入了系统变量,ansible在执行Playbook之前会收集一些比较常用的系统变量,在Playbook中不需要定义直接就可以使用。1
2- debug:
msg: "System {{ inventory_hostname }} has gateway {{ ansible_default_ipv4.gateway }}"
通过参数var定义需要打印的变量,变量可以是系统变量,也可以是动态的执行结果,通过关键字regester注入到变量中。1
2
3- name: Display all variables/facts known for a host
debug:
var: hostvars[inventory_hostname]["ansible_default_ipv4"]["gateway"]
通过Ad Hoc Command执行1
ansible all -m debug -a 'msg={{ inventory_hostname }}'
1 | ansible all -m setup |
引用收集到的Facts信息1
2
3
4
5
6#可以在playbook中这样引用第一个硬盘的模型:
{{ ansible_devices.sda.model }}
#同样,作为系统报告的主机名如以下所示:
{{ ansible_nodename }}
#不合格的主机名显示了句号(.)之前的字符串:
{{ ansible_hostname }}
关闭fact1
2- hosts: whatever
gather_facts: no
参数1
2
3
4
5
6
7
8
9
10
11
12
13mode:
backup:参数为yes的时候,如果发生了拷贝操作,那么会先备份目标节点上的原文件
checksum:
src:源地址。如果路径以/结尾,则只复制路径中的内容;不以/结尾,该目录及其内部内容都会被复制
remote_src:当mode=preserve有效,
dest:复制的目的路径。当src指定为一个目录时,dest也必须为目录;当dest以/结尾或路径不存在时或者src是一个目录时,dest指明的路径将被创建;当src和dest都是文件时,dest的父目录不存在将不会被创建,任务将会失败
content:
follow:
validate:验证文件的命令。一般需要验证拷贝后的文件,所以%s可以指代拷贝后的文件。只有复制和验证都完成才算执行成功
force:为yes时将会覆盖与源路径不同的目的内容,为no时仅会传输远程目的路径不存在的内容,默认为yes
owner:类似于chown命令,改变所属主
group:类似于chown命令,改变所属组
mode:设置文件权限。mode设置权限可以是用数字,也可以是符号的形式”u=rw,g=r,o=r”和”u+rw,g-wx,o-rwx”
例子1
2
3
4- copy:
src: /mine/sudoers
dest: /etc/sudoers
validate: 'visudo -cf %s' #visudo -cf /etc/sudoers是验证sudoers文件有没有语法错误的命令。
指定替换个部分用变量来表示,template使用的是python的j2模版引擎,变量的表示法是
参数1
2
3
4
5
6backup:建立包含时间戳的备份,默认为no
src:本地Jinjia2模版的template文件位置
dest:远程节点上的绝对路径,用于放置template文件
owner:设置文件或目录所属主
group:设置文件或目录所属组
mode:设置远程节点上的template文件权限。类似Linux中chmod的用法
例子1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16- template:
src: /mytemplates/foo.j2
dest: /etc/file.conf
owner: bin
group: wheel
mode: 0644
- template:
src: config.ini.j2
dest: /share/windows/config.ini
newline_sequence: '\r\n'
- template:
src: /mine/sudoers
dest: /etc/sudoers
validate: '/usr/sbin/visudo -cf %s' #验证配置文件是否正确
参数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17attributes:改变文件、目录属性,相当于chattr命令
follow:如果原来的文件是link,拷贝后是否依旧是link,默认为yes
force:需要在两种情况下强制创建软链接,一种是源文件不存在但之后会建立的情况下;另一种是目标软链接已存在,需要先取消之前的软链,然后创建新的软链,默认为no
state:参数:
directory:如果目录不存在,创建目录
file:即使文件不存在,也不会被创建,默认参数
link:创建软链接
hard:创建硬链接
touch:如果文件不存在,则会创建一个新的文件,如果文件或目录已存在,则更新其最后修改时间
absent:删除目录、文件或者取消链接文件
src:要被链接的源文件的路径,只应用于state=link的情况
dest:当state=link时,表示创建的指向src的链接文件放置路径
path:需要管理的文件路径
recurse:递归的设置文件的属性,只对目录有效
owner:设置文件或目录所属主
group:设置文件或目录所属组
mode:设置远程节点上的文件权限。类似Linux中chmod的用法
例子1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18- file:
path: /etc/foo.conf
owner: foo
group: foo
mode: 0644
- file:
src: /file/to/link/to
dest: /path/to/symlink
owner: foo
group: foo
state: link
- file:
src: '/tmp/{{ item.src }}'
dest: '{{ item.dest }}'
state: link
with_items:
- { src: 'x', dest: 'y' }
- { src: 'z', dest: 'k' }
参数1
2
3
4
5
6
7
8
9
10
11state:present表示添加用户,absent表示删除用户
name:用户名
create_home:是否创建家目录,默认为yes
home:用于设定家目录路径
uid:用户的uid
group:所属组,即私有组
groups:附加组
password:设置用户密码(加密过后的值)
remove:当state=absent时使用,等同于userdel --remove,删除用户并删除家目录及其文件
shell:设置用户所使用的shell
system:创建一个用户时是否加入管理员组,对已存在用户无效
例子1
2
3ansible mysql -m user -a 'name="mysql" group="mysql" create_home=no shell="/sbin/nologin"'
ansible mysql -a 'cat /etc/passwd' | grep mysql #查看是否添加成功
ansible mysql -m user -a 'state=absent name="mysql"' #删除用户
参数1
2
3state:present表示安装,latest表示升级最新版,absent表示删除
name:指明包名
list:指定不同参数列出相应信息:packages, installed, updates, available, repos
例子1
ansible all -m yum -a 'list=installed'
参数1
2
3
4
5
6
7state:包含多个选项:reloaded, restarted, running, started, stopped
arguments:
enable:是否开机启动
name:服务名
pattern:
runlevel:
sleep:
参数1
2
3
4
5state:present表示添加crontab任务;absent表示移除crontab任务
job:指明运行的命令是什么
name:crontab任务的名字
minute:指明分钟周期,未指明默认为*
day:指明天周期,未指明默认为*
例子1
2
3ansible mysql -m cron -a 'state=present minute="*/10" job="/usr/bin/echo hello" name="test cron job"'
ansible mysql -a 'crontab -l' #查看是否成功添加
ansible mysql -a 'cat /var/spool/cron/root' #查看生成的文件
参数1
2
3
4
5chdir:运行命令前进入指定的目录
creates:创建一个文件,如果已存在则不会运行此步骤
executable:更改用于执行命令的Shell,必须使用绝对路径
removes:删除一个文件,如果不存在则不会进行
stdin:设置执行的命令的stdin为一个特定来源
例子1
2
3
4
5- name:
shell: somescript.sh >> somelog.txt #重定向输出
args:
chdir: somedir/ #改变目录
creates: somelog.txt #当somelog.txt不存在时创建它
command:在远程节点上面执行命令,不支持$HOME和”<”, “>”, “|”, “;” and “&”
默认的模块,表示在被管理主机上运行一个命令。对于command模块,-a不再是指定参数,而是命令本身,所以该模块无法传递参数或变量
例子
1 | ansible all -m command -a "date" |
script:将本地脚本复制到远程主机并运行
参数1
2
3chdir:运行命令前进入指定的目录
creates:创建一个文件,如果已存在则不会运行此步骤
removes:删除一个文件,如果不存在则不会进行
例子1
2
3- script: /some/local/script.sh --some-arguments 1234
args:
creates: /the/created/file.txt
ansbile在管理每一个主机时,这些主机在被运行管理命令之前,会首先向ansible节点报告自己主机当前的各种可能被ansible主机用到的状态信息,如操作系统版本、ip地址等信息,这些信息都是以变量的形式,ansible主机可以在jinjia2中调用,为不同的服务器生成不同的配置文件。
参数1
2
3
4
5backup:是否生成一个包含时间戳的备份
src:指明本地j2模板文件地址
dest:指明远程主机应用配置文件的路径
mode:设置文件权限
validate:验证配置文件是否正确
例子1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20- template:
src: /mytemplates/foo.j2
dest: /etc/file.conf
owner: bin
group: wheel
mode: 0644
#配置sudoers文件并验证是否正确
- template:
src: /mine/sudoers
dest: /etc/sudoers
validate: '/usr/sbin/visudo -cf %s'
#配置sshd并验证配置文件是否正确
- template:
src: etc/ssh/sshd_config.j2
dest: /etc/ssh/sshd_config
owner: root
group: root
mode: '0600'
validate: /usr/sbin/sshd -t -f %s
backup: yes
Ansible提供的命令行工具,用于在ansible中快速执行,并且不需要保存的命令。
例子1
2
3
4
5ansible all -m setup #查看远程主机facts
ansible all -m ping -u jaydenz #检查所有的远程主机,是否以jaydenz用户创建了ansible主机可以访问的环境。
ansible all -a "/bin/echo hello" #在所有的远程主机上,以当前bash的同名用户,在远程主机执行“echo hello”
ansible web -m copy -a "src=/etc/hosts dest=/tmp/hosts" #拷贝文件/etc/host到远程主机(组)web,位置为/tmp/hosts
ansible web -m git -a "repo=git://foo.example.org/repo.git dest=/srv/myapp version=HEAD" #git一个项目到web主机本地目录
Playbook是由一个或多个play组成的列表,针对每一组server的所有操作就组成一个play。Playbook的主要功能在于将事先归并为一组的主机装扮成事先通过ansible中的task定义好的角色。从根本上来讲,所谓task无非是调用ansible的一个module。在写Playbook的时候,一定要记住在hosts、模块名等后面带空格,否则会报错。
执行Playbook:1
ansible-playbook deploy.yml
在运行Playbook的时候也可以传递一些变量供Playbook使用:1
ansible-playbook test.yml --extra-vars "hosts=mysql user=jaydenz"
hosts和remote_users
1 | - hosts: mysql #指明执行任务的主机,可以是一个或多个由冒号分隔主机组 |
tasks:任务,调用模块完成某操作
Playbook的核心,定义按顺序执行的动作action,每个action调用一个ansbile module,如果中途发生错误所有任务将会回滚。每个Task都需要使用- name指定一个名用于Playbook的执行结果输出,如果未提供name,则Action的结果将用于输出。1
2
3
4tasks:
- name: make sure apache is running
service: name=httpd state=running #定义一个Action,建议使用module:module_parameter=module_value格式的语法
#如果action一行的内容过多,也中使用在行首使用几个空白字符进行换行。
在众多模块中,只有command和shell模块仅需要给定一个列表而无需使用“key=value”格式,例如:1
2
3tasks:
- name: disable selinux
command: /sbin/setenforce 0
如果命令或脚本的退出码不为零,可以使用如下方式替代:1
2
3tasks:
- name: run this command and ignore the result
shell: /usr/bin/somecommand || /bin/true
或者使用ignore_errors来忽略错误信息:1
2
3
4tasks:
- name: run this command and ignore the result
shell: /usr/bin/somecommand
ignore_errors: True
vars
1 | - hosts: webservers |
register
把任务的输出定义为变量,然后用于其他任务。1
2
3
4tasks:
- shell: /usr/bin/foo
register: foo_result #把shell的输出赋值给foo_result
ignore_errors: True
在task后添加when子句即可使用条件测试;when语句支持Jinja2表达式语法。例如:1
2
3
4tasks:
- name: "shutdown Debian flavored systems"
command: /sbin/shutdown -h now
when: ansible_os_family == "Debian"
when语句中还可以使用Jinja2的大多“filter”,例如要忽略此前某语句的错误并基于其结果(failed或者sucess)运行后面指定的语句,可使用类似如下形式:1
2
3
4
5
6
7
8
9
10tasks:
- 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语句中还可以使用facts或playbook中定义的变量。
用于当监控的资源发生变化时采取一定的操作。使用handlers定义一个或一组action,每个action使用name关键字指明名称,在要监控的资源处定义notify并指明需要调用的handlers的名称,handlers也属于tasks1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25- hosts: web
vars:
http_port: 80
max_clients: 200
remote_user: root
tasks:
- name: ensure apache is at the latest version
yum: pkg=httpd state=latest
- name: Write the configuration file
template: src=templates/httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf
notify: #定义一个notify监控配置文件是否发送改变
- restart apache #调用handlers中名称为restart apache的action
- name: Write the default index.html file
template: src=templates/index.html.j2 dest=/var/www/html/index.html
- name: ensure apache is running
service: name=httpd state=started
handlers: #定义一个handlers
- name: restart apache #定义一个处理动作
service: name=httpd state=restarted
- name: ping server
ping:
ansilbe自1.2版本引入的新特性,用于层次性、结构化地组织playbook。roles能够根据层次型结构自动装载变量文件、tasks以及handlers等。要使用roles只需要在playbook中使用include指令即可。简单来讲,roles就是通过分别将变量、文件、任务、模块及处理器放置于单独的目录中,并可以便捷地include它们的一种机制。角色一般用于基于主机构建服务的场景中,但也可以是用于构建守护进程等场景中。
一个项目的目录结构1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20site.yml
webservers.yml
fooservers.yml
roles/
common/ #定义一个名为common的role
files/
templates/
tasks/
handlers/
vars/
defaults/
meta/
webservers/
files/
templates/
tasks/
handlers/
vars/
defaults/
meta/
该项目的一个playbook1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36- hosts: webservers
roles:
- common #使用名为common的role
- webservers
这个playbook 为一个角色 ‘x’ 指定了如下的行为:
如果 roles/x/tasks/main.yml 存在, 其中列出的 tasks 将被添加到 play 中
如果 roles/x/handlers/main.yml 存在, 其中列出的 handlers 将被添加到 play 中
如果 roles/x/vars/main.yml 存在, 其中列出的 variables 将被添加到 play 中
如果 roles/x/meta/main.yml 存在, 其中列出的 “角色依赖” 将被添加到 roles 列表中 (1.3 and later)
所有 copy tasks 可以引用 roles/x/files/ 中的文件,不需要指明文件的路径。
所有 script tasks 可以引用 roles/x/files/ 中的脚本,不需要指明文件的路径。
所有 template tasks 可以引用 roles/x/templates/ 中的文件,不需要指明文件的路径。
所有 include tasks 可以引用 roles/x/tasks/ 中的文件,不需要指明文件的路径。
在 Ansible 1.4 及之后版本,你可以为”角色”的搜索设定 roles_path 配置项。使用这个配置项将所有的 common 角色 check out 到一个位置,以便在多个 playbook 项目中可方便的共享使用它们。
#可以使用参数化的 roles,这种方式通过添加变量来实现
- hosts: webservers
roles:
- common
- { role: foo_app_instance, dir: '/opt/a', port: 5000 }
- { role: foo_app_instance, dir: '/opt/b', port: 5001 }
- { role: some_role, when: "ansible_os_family == 'RedHat'" } #为roles设置触发条件
- { role: foo, tags: ["bar", "baz"] } #给roles分配指定的tags
#注意:role、dir、port是变量名,冒号后面的是变量值。
#定义一些 tasks,让它们在 roles 之前以及之后执行,如果对tasks应用了tags,需确保给pre_tasks以及post_tasks也同样应用 tags,并且将它们一并传递。
- hosts: webservers
pre_tasks:
- shell: echo 'hello'
roles:
- { role: some_role }
tasks:
- shell: echo 'still busy'
post_tasks:
- shell: echo 'goodbye'
角色依赖:可以自动地将其他roles拉取到现在使用的role中,保存在roles目录下的 meta/main.yml 文件中。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30#roles/myapp/meta/main.yml示例
dependencies:
- { role: common, some_parameter: 3 }
- { role: apache, port: 80 }
- { role: postgres, dbname: blarg, other_parameter: 12 }
#角色依赖可以通过绝对路径指定,如同顶级角色的设置:
dependencies:
- { role: '/path/to/common/roles/foo', x: 1 }
#角色依赖总是在role(包含角色依赖的role)之前执行,并且是递归地执行。默认情况下,作为角色依赖被添加的role只能被添加一次,如果另一个 role 将一个相同的角色列为角色依赖的对象,它不会被重复执行。
#但这种默认的行为可被修改,通过添加 allow_duplicates: yes 到 meta/main.yml 文件中。 比如,一个role名为car,它可以添加名为wheel的 role到它的角色依赖中:
dependencies:
- { role: wheel, n: 1 }
- { role: wheel, n: 2 }
- { role: wheel, n: 3 }
- { role: wheel, n: 4 }
wheel角色的 meta/main.yml 文件包含如下内容:
allow_duplicates: yes
dependencies:
- { role: tire }
- { role: brake }
最终的执行顺序是这样的:
tire(n=1)
brake(n=1)
wheel(n=1)
tire(n=2)
brake(n=2)
wheel(n=2)
...
car
1 | tasks: |
特殊tags: always,
当有需要重复性执行的任务时,可以使用迭代机制。其使用格式为将需要迭代的内容定义为item变量引用,并通过with_items语句来指明迭代的元素列表即可。例如:1
2
3
4
5- name: add several users
user: name={{ item }} state=present groups=wheel
with_items:
- testuser1
- testuser2
上面语句的功能等同于下面的语句:1
2
3
4- name: add user testuser1
user: name=testuser1 state=present groups=wheel
- name: add user testuser2
user: name=testuser2 state=present groups=wheel
注意:with_items中的列表值也可以是字典, 但引用时要使用item.KEY
1 | tasks: |
配置生成密钥
1 | ssh-keygen -t rsa -P '' |
测试连通性
1 | ansible all -m ping |
执行Playbook
1 | yum -y install epel-release |
1 | #在extras源中 |
Ansible官方模块说明:http://docs.ansible.com/ansible/latest/modules/list_of_all_modules.html
个人常用Playbook:https://github.com/Jaydenz/playbook
Playbook指南:http://ansible-tran.readthedocs.io/en/latest/docs/playbooks.html
]]>预启动执行环境(Preboot eXecute Environment,PXE),
该技术基于C/S构架,提供了一种使用网络接口(Network Interface)启动计算机的机制。这种机制让计算机的启动可以不依赖本地数据存储设备(如硬盘)或本地已安装的操作系统。PXE技术通常用于自动部署操作系统,通过读取Kickstart文件中的配置可以实现无人值守安装。
PXE技术依赖于DHCP、TFTP、File Server
CentOS 7 Server 1台
支持PXE技术的Client 2台
安装相关服务
1 | yum -y install dhcp tftp-server syslinux vsftpd |
配置DHCP服务
1 | vi /etc/dhcp/dhcp.conf |
修改TFTP服务启动脚本
1 | vi /usr/lib/systemd/system/tftp.service |
建立tftpboot目录
1 | mkdir /tftpboot |
修改SELinux设置
1 | semanage fcontext -a -t public_content_t "/tftpboot(/.*)?" |
复制syslinux文件到启动目录
1 | cd /usr/share/syslinux/ |
修改目录权限
1 | chmod 644 -R /tftpboot |
建立提供Linux开机的核心目录
1 | mkdir /tftpboot/pxelinux.cfg |
复制安装光盘文件到FTP分享目录
1 | mount CentOS-7-x86_64-Minimal-1708.iso /mnt/cdrom |
复制PXE启动文件到tftp服务器分享目录
1 | cd /var/ftp/pub/images/pxeboot |
在FTP服务的目录下建立一个Kickstart文件
1 | touch /var/ftp/pub/ks.cfg |
设置PXE启动菜单
1 | vim /tftpboot/pxelinux.cfg/default |
设定防火墙
1 | firewall-cmd --permanent --add-service=ftp |
启动各项服务
1 | systemctl daemon-reload |
Client端建议将PXE网卡启动顺序放在第二位,也就是硬盘之后,这样当安装程序完成后重启时不会再次进入PXE。
Kickstart是redhat开发的开源工具,它可以将操作系统安装步骤记录到一个纯文本文件当中,安装程序通过读取该文件可以实现操作系统自动化安装。
CentOS 7 Kickstar文件实例1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132#platform=x86, AMD64, or Intel EM64T
#version=CentOS7
#本配置文件用于自动安装CentOS 7.4 x64位系统
#####Kickstart安装参数#####
#设置安装界面类型,参数为text | graphical | cmdline,默认为graphical
graphical
#全新安装或是升级
install
#设定安装文件来源
##使用NFS
#nfs --server=192.168.1.128 --dir=/CentOS/CentOS7x64
##使用HTTP/HTTPS、FTP、file
url --url=ftp://192.168.213.1/CentOS/CentOS7x64
#限制安装程序操作的磁盘
##--drives=sda,hda,...:使安装程序忽略指定的磁盘。
##--only-use:指定安装程序要使用的磁盘列表,忽略其他所有磁盘。
#ignoredisk --only-use=sda
#第一次启动是否运行配置向导,图像化界面建议开启
firstboot --disable
#安装后自动重启。注意,安装后应设置为从先硬盘启动,否则会重复安装
reboot
#设置系统验证信息
auth --useshadow --passalgo=sha512
#####操作系统配置#####
#设定键盘布局
keyboard us
#设定系统语言
lang en_US
#设定系统时区
timezone Asia/Shanghai
#主机名设置
network --hostname=localhost.localdomain
#配置网络地址获取方式
network --bootproto=dhcp --onboot=yes
#####用户和组相关配置#####
#设置root密码,加密密码可以使用openssl生成
rootpw --iscrypted $1$y10oqPAp$qCWLZ7KzL/QgFYlg3C2R10 #采用加密记录
#rootpw --plaintext 123456 #采用明文记录
#####系统安全相关选项#####
#防火墙配置
##--enabled:启用防火墙
##--disabled:禁用防火墙
##--trust=:允许所有流量通过该防火墙进出指定设备。要列出一个以上的设备,请使用 --trust ens33 --trust ens37
##可以使用以下服务中的一个或多个来替换,从而允许指定的服务通过防火墙。
##--ssh
##--smtp
##--http
##--ftp
##--port=:使用 端口:协议 格式指定允许通过防火墙的端口。例如,如果想允许 IMAP 通过您的防火墙,可以指定 imap:tcp。还可以具体指定端口号码,要允许 UDP 分组在端口 1234 通过防火墙,输入 1234:udp。要指定多个端口,用逗号将它们隔开。
firewall --disabled
#SELinux选项
selinux --disabled
#是否配置X图形界面
skipx
#####磁盘于引导选项#####
#bootloader指定指定如何安装引导装载程序
##--location=:指定引导记录的写入位置。
#mbr:默认参数,具体要看驱动器是MBR还GPT,会自动决定,在大多数情况下不需要指定这个选项。
#partition:在包含内核的分区的第一个扇区中安装引导装载程序。
#none:不安装引导装载程序。
##--append=:指定内核参数。要指定多个参数,使用空格分隔它们。例如:bootloader --location=mbr --append="hdd=ide-scsi ide=nodma"
bootloader --location=mbr
#clearpart用于指定如何清除原有分区
##--all:清除所有分区
##--drivers=:用于指定从哪个驱动器清除分区,例子:clearpart --drives=hda,hdb --all,清除主 IDE 控制器中前两个驱动器上所有分区
##--list= - 指定要清理的分区。这个选项覆盖--all和--linux选项,并可跨不同驱动器中使用。例如:clearpart --list=sda2,sda3,sdb1
##--linux - 删除所有 Linux 分区。
##--none(默认)- 不删除任何分区。
clearpart --all
#磁盘分区设定
part / --fstype="xfs" --size=10000
part /boot --fstype="xfs" --size=200
#part /boot/efi --fstype=efi --size=200 #UEFI引导安装时需要,传统LEGACY引导时可屏蔽此选项
part swap --fstype="swap" --size=4000
#如果是要LVM分区,则考虑以下分区
#part /boot --fstype ext4 --size=100
#part swap --fstype=swap --size=2048
#part pv26 --size=100 --grow
#volgroup VG00 --pesize=32768 pv26
#logvol / --fstype ext4 --name=LVroot --vgname=VG00 --size=29984
#logvol /data --fstype ext4 --name=LVdata --vgname=VG00 --size=100 --grow
#####软件包选项#####
#%packages设定所需要安装的软件包,支持传递参数,--ignoremissing:忽略所有在这个安装源中缺少的软件包、组及环境,
%packages --ignoremissing
#@表示一组软件包,常见安装类型如下:
##@X Window System
##@Desktop
##@Sound and Video
##@network-server
##@performance
##@system-admin-tools
@core
@base
#指定独立的软件包名可以安装单独的软件包,也可以在软件包名称中使用星号(*)作为通配符。
tree
tuned
tuned-utils
ypbind
nfs-utils
vim-enhanced
wget
openssh-server
#-表示从默认软件包中移除的软件包
-lvm2
-nano
%end #%packages结尾
#####安装脚本(非必需)#####
%pre #安装前脚本
%end #安装前脚本结尾
%post #安装后脚本
%end #安装后脚本结尾
在安装CentOS7时如果一直显示Downloading package metadata或者其他无法获取软件源的问题
首次启动 DHCP 服务器时,如果没有 dhcpd.leases 文件就会失败。如果没有该文件,可使用命令 touch /var/lib/dhcpd/dhcpd.leases 生成该文件。
Kickstart官方语法指导:https://access.redhat.com/documentation/zh-cn/red_hat_enterprise_linux/7/html/installation_guide/sect-kickstart-syntax
]]>个人电脑(PC)的出现极大的普及了计算机的应用,从1980年IBM推出了以Intel的x86构架搭配微软的MS-DOS操作系统的PC以来,现代PC的硬件设备几乎都是由Wintel架构垄断的,不过今天我们并不深入探讨历史,而是介绍一下这种构架的电脑启动的原理。
从按下电源按钮开始,计算机进行加电自检(Power On Self Test ,POST),POST过后初始化用于启动的硬件(磁盘、键盘控制器等),然后从主板的ROM或Flash芯片中加载基本输入/输出系统(Basic Input/Output System,BIOS)到内存中,进行初始化,随后从CMOS中读取用户自定义的设置,这时候根据设置信息,BIOS通过启动顺序(BootSequence)列表查找对应设备上的启动文件。
在使用MBR格式的磁盘时,这种格式的磁盘第一个扇区保存有主引导记录(Master Boot Becord,MBR),这个记录有512字节大小,如果最后两个字节是0x55和0xAA,表明这个设备可以用于启动,那么它将接管BIOS传递的控制权,否则,BIOS将查找下一个设备。
主引导记录由三部分组成1
2
3第1-446字节:调用操作系统的机器码
第447-510字节:分区表(Partition table)
第511-512字节:主引导记录签名(0x55和0xAA)
由于分区表的长度只有64个字节,一个分区需要占用16个字节,所以MBR格式硬盘最多只能存在四个主分区。MBR格式磁盘的弊端不仅如此:由于主分区只有16个字节,它又由6个部分组成:1
2
3
4
5
6第1个字节:如果为0x80,就表示该主分区是激活分区
第2-4个字节:主分区第一个扇区的物理位置(柱面、磁头、扇区号等等)
第5个字节:主分区类型
第6-8个字节:主分区最后一个扇区的物理位置
第9-12字节:该主分区第一个扇区的逻辑地址
第13-16字节:主分区的扇区总数,决定了这个主分区的长度。
扇区总数为最多不超过2的32次方,如果每个扇区为512个字节,就意味着单个分区最大不超过2TB。再考虑到扇区的逻辑地址也是32位,所以单个硬盘可利用的空间最大也不超过2TB。如果想使用更大的硬盘,只有2个方法:一是提高每个扇区的字节数,二是增加扇区总数。为了解决这些问题,人们发明了GPT格式的硬盘。
继续前面的话题,当BIOS在存储设备找到MBR并且MBR存在启动信息时,这时又会遇到三种情况
在Windows系统中
在Linux系统中
随着计算机技术的不断进步,老旧的BIOS已经不能适应现在的要求,于是统一可扩展固件接口(Unified Extensible Firmware Interface,UEFI)技术出现了。
与BIOS相比,UEFI使用模块化理念,它可以加载ESP分区中的EFI应用程序和EFI驱动程序,支持安全启动、GUI等新功能,这些取决于PC制造商是否添加,它还能实现BIOS的兼容模式。只需要把操作系统的引导程序做成一个EFI应用程序就可以使用UEFI加载引导了,如果存在多个操作系统,只需要在UEFI引导界面选择相应的程序就行了。
UEFI的启动流程
首先系统开机,然后进行自检(Power On Self Test,POST),随后UEFI 固件被加载,并由它初始化启动要用的硬件。随后启动UEFI引导管理器,它将通过NVRAM中定义的配置决定如何加载UEFI驱动和UEFI可执行文件。已启动的UEFI应用还可以启动其他应用比如bootloader或者启动内核及initramfs,我们只要把操作系统的bootloader做成一个EFI可执行文件,放入EFI分区对应的目录就可以使用UEFI读取启动了。
全局唯一标识分区表(GUID Partition Table,GPT),与MBR最大4个分区表项的限制相比,GPT对分区数量没有限制,其分区数量只受操作系统限制,GPT可管理硬盘大小达到了18EB。只有基于UEFI平台的主板才支持GPT分区引导启动。
存储设备上的Bootloader接过BIOS的控制权后进行Linux的内核引导,这时根文件系统并未挂载,而内核要读取文件系统就必须要挂载根文件系统,但此时并没有文件系统能提供挂载点,为了解决这个问题内核就先读取存储介质中的初始RAM磁盘(Initialized RAM Disk,initrd)并将其加载到内存作为一个临时的根目录,加载一部分驱动,最重要的是用来挂载真正的根文件系统(这时是以只读方式),然后切换到真正的根文件系统,完成初始化任务,最后内核会运行第一个程序/sbin/init并将系统控制权交给它。
Linux的init程序经过了好几个版本,以CentOS为例
系统版本 | init程序 |
---|---|
CentOS 5及以前 | SysV,配置文件: /etc/inittab |
CentOS 6 | Upstart,配置文件: /etc/inittab, /etc/init/*.conf |
CentOS 7 | Systemd,配置文件: /usr/lib/systemd/system、 /etc/systemd/system |
在使用SysV的系统上,内核文件加载之后,就开始运行第一个程序/sbin/init,它负责初始化系统环境,他的pid为1,其他所有进程都由它衍生,都是他的子进程。
在采用systemd的系统上,运行的第一个程序为/usr/lib/systemd/systemd,它的的pid同样为1,也是所有进程的父进程。
运行级别用于设定Linux操作系统不同的运行模式,运行级别控制Linux系统通过init程序为不同场合分配不同的开机启动程序。
Linux系统有7个运行级别(runlevel):
运行级别 | 说明 |
---|---|
runlevel 0 | 系统停机、关机,系统默认运行级别不能设为0,否则不能正常启动 |
runlevel 1 | 单用户状态,root权限,用于系统维护,禁止远程登陆,无网络连接 |
runlevel 2 | 多用户状态,无网络连接,不运行守护进程,无NFS |
runlevel 3 | 完全的多用户状态,有NFS,登陆后进入控制台命令行模式 |
runlevel 4 | 系统未使用,保留 |
runlevel 5 | 多用户,X11控制台,登陆后进入图形GUI模式 |
runlevel 6 | 系统正常关闭并重启,默认运行级别不能设为6,否则不能正常启动 |
init程序读取/etc/inittab定义的运行级别来进行系统初始化。
在systemd中runlevel已被target取代,systemd会读取/etc/systemd/system/default.target来决定启动到什么样的target(sysv中称为runlevel),这是一个符号链接,指向/usr/lib/systemd/system/下相应的target,由于可以实现并行启动,systemd没有严格的启动顺序。在CLI环境default.target指向/lib/systemd/system/multi-user.target,systemd通过读取target文件进行下一步操作,比如运行/usr/lib/systemd/system/sysinit.target开始系统初始化,这些都依赖于相应的target文件中的配置。
当设置好了runlevel之后,init程序会首先执行/etc/rc.d/rc.sysinit脚本,它是每个runlevel都要执行的重要脚本,它主要进行以下操作:1
2
3
4
5
6
7
8
9
10
111.设置主机名称;
2.设置启动的欢迎信息;
3.激活udev和SELinux
4.挂载/etc/fstab文件中定义的所有有效文件系统;
5.激活各个swap设备;
6.检测rootfs,并且以读写的方式重新挂载rootfs;
7.设置系统时间;
8.根据/etc/sysctl.conf文件设置内核参数;
9.激活lvm和软RAID等高级逻辑设备;
10.加载额外的设备的驱动程序;
11.完成清理工作;
然后init程序根据相应的级别加载对应配置的程序,所有由rc脚本关闭或启动的链接文件的源文件都存在于/etc/rc.d/init.d,通过链接的方式放入不同的runlevel文件夹。比如当引导至运行级别 5 时,init 程序会在 /etc/rc.d/rc5.d/ 目录中查看并确定要启动和停止的进程。当init程序启动完对应的程序与守护进程后,这是系统环境基本已经搭建好了。
在systemd中,/usr/lib/systemd/system/sysinit.target、/usr/lib/systemd/system/basic.target等target会根据对应的依赖关系启动,执行相应的系统初始化任务。
用户可以通过三种方式登陆Linux
这几种登陆方式会读取不同的配置文件,在Bash Shell相关博文会做详细介绍。
用户登陆系统后,开机过程就算完成了。
简化的Linux系统启动流程
BIOS + MBR1
POST --> BIOS --> MBR --> Bootloader --> kernel + ramdisk --> rootfs(read-only) --> /sbin/init(systemd) --> login
UEFI + GPT1
POST --> UEFI --> EFI Application(Bootloader) --> kernel + ramdisk --> rootfs(read-only) --> /sbin/init(systemd) --> login
Overview of systemd for RHEL 7:https://access.redhat.com/articles/754933
]]>Linux使用lsof命令lsof /PATH/TO/SOMEFILE #查看某文件被哪个程序占用
找到之后使用ps命令找出pid结束进程即可。
lsof命令还可以实现其他很多功能,简单举几个例子:1
2
3lsof -u USERNAME #查看某用户打开的文件信息
lsof -c mysql #列出某命令打开的文件
lsof -i tcp #列出所有tcp网络连接信息
自理查德·斯托曼于1983年发起GNU计划时起,开源的理念逐渐在互联网传播。开源运动后来分化出了开源软件和自由软件两个说法,自由软件根据自由软件基金会的定义是指一类可以不受限制地自由使用、复制、研究、修改和分发的,尊重用户自由的软件,而开源软件是指一类源代码可以任意获取的计算机软件,这种软件的版权持有人在软件协议的规定之下保留一部分权利并允许用户学习、修改、增进提高这款软件的质量,而对它们进行约束的就是开源协议。目前互联网存在诸多开源协议,每种协议的要求各不相同,下面介绍一下几个常见的开源协议。
GNU General Pubilc Licence(GPL)是一种广泛使用的开源软件许可证,可以保证终端用户得自由运行,学习,共享和修改软件,目前已经发布到了第三版。GPL协议最主要的几个原则:
简单的讲,选择GPL协议,任何衍生代码都必须开源,并且以相同的许可条款分发,这就是为什么我们能用到各式各样的免费Linux发行版的原因。
LGPL是GPL的一个为主要为类库使用设计的开源协议。和GPL要求任何使用/修改/衍生自GPL类库的的软件必须采用GPL协议不同。LGPL允许商业软件通过类库引用(link)方式使用LGPL类库而不需要开源商业软件的代码。这使得采用LGPL协议的开源代码可以被商业软件作为类库引用并发布和销售。
但是如果修改LGPL协议的代码或者衍生,则所有修改的代码,涉及修改部分的额外代码和衍生的代码都必须采用LGPL协议。因此LGPL协议的开源代码很适合作为第三方类库被商业软件引用,但不适合希望以LGPL协议代码为基础,通过修改和衍生的方式做二次开发的商业软件采用。
GPL/LGPL都保障原作者的知识产权,避免有人利用开源代码复制并开发类似的产品。
BSD开源协议是一个给于使用者很大自由的协议。基本上使用者可以”为所欲为”,可以自由的使用,修改源代码,也可以将修改后的代码作为开源或者专有软件再发布。但”为所欲为”的前提当你发布使用了BSD协议的代码,或则以BSD协议代码为基础做二次开发自己的产品时,需要满足三个条件:
BSD代码鼓励代码共享,但需要尊重代码作者的著作权。BSD由于允许使用者修改和重新发布代码,也允许使用或在BSD代码上开发商业软件发布和销售,因此是对商业集成很友好的协议。而很多的公司企业在选用开源产品的时候都首选BSD协议,因为可以完全控制这些第三方的代码,在必要的时候可以修改或者二次开发。
MIT 协议可能是几大开源协议中最宽松的一个,核心条款是:
该软件及其相关文档对所有人免费,可以任意处置,包括使用,复制,修改,合并,发表,分发,再授权,或者销售。唯一的限制是,软件中必须包含上述版 权和许可提示。这意味着:
Apache Licence是著名的非盈利开源组织Apache采用的协议。该协议和BSD类似,同样鼓励代码共享和尊重原作者的著作权,同样允许代码修改,再发布作为开源或商业软件。需要满足的条件也和BSD类似:
Hexo是一款基于Node.js的静态博客框架,依赖少易于使用,再加上可以部署到Github上面,成为很多程序员首选的个人博客框架。
Hexo依赖于Node.js,下载对应版本的Node.js运行环境安装
检查Node.js是否安装成功
node -v
检测npm是否安装成功
npm -v
安装Hexo
npm install -g hexo-cli
初始化博客
进入到自定义的空目录,输入
hexo init
设置Git,打开命令行,输入
1 | git config --global user.name "你的GitHub用户名" |
生成ssh密钥ssk-keygen -t rsa
在命令行输入
hexo n "博客名字"
在blog根目录下的source文件夹中的_post文件夹中多了一个博客名字.md文件,然后使用Markdown编辑器进行编辑即可
在命令行输入
hexo server
使用浏览器访问localhost:4000 即可预览博文效果。
1 | npm update hexo -g #升级 |
写好博文并且样式无误后,在命令输入
hexo g
hexo d
生成、部署网页。随后可以在浏览器中输入域名浏览。
1 | hexo new page "categories" |
1 | --- |
tags/index.md1
2
3
4
5---
title: 标签
date: 2018-4-30 11:59:10
type: "tags"
---
1 | title: {{ title }} |
scaffolds/post.md1
2
3title: {{ title }}
date: {{ date }}
tags: {{ tags }}
1 | #图片路径 |
这样图片既可以在首页内容中访问到,也可以在文章正文中访问到。
post_asset_folder: true
$ hexo new post_name
1 | #图片路径 |
但是图片只能在文章中显示,但无法在首页中正常显示。
如果希望图片在文章和首页中同时显示,可以使用标签插件语法。1
2
3
4#图片路径
_posts/post_name/image.jpg
#Markdown引用语法
{% asset_img image.jpg This is an image %}
然后继续执行hexo deploye指令进行部署。
修改hexo-theme-indigo/source/css/_partial/variable.less
把@contentWidth: 960px;
改成@contentWidth: 90%;
修改主题config文件cdn: false