Ansible 初体验
记得刚来上海的时候,叶神给了我一本奶牛书:《奔跑吧, Ansible》。时隔一年,我居然有了写 Ansible 脚本的需求。
安装
pip install ansible
配置主机
在安装完 ansible 之后,需要在 /etc 下创建一个 ansible 文件夹,并在里面添加一个 hosts 文件,因为 ansible 会默认在 /etc/ansible/hosts
中寻找主机的配置。
因为 macOS 下 etc 文件夹不能编辑或者因为其他原因,可以通过 -i 指定 hosts 的位置。
可以创建一个 hosts 文件:
[local]
10.8.0.164
[blog]
150.95.155.202 ansible_user=user ansible_port=2222
[]
内为 target, 可以通过 targets 来对主机进行分组管理。
每一行包含一个 ip 或者域名,host 好支持一些表达式。
在每个 ip 后面可以加一些特殊的参数,比如 SSH 用户名,SSH 端口,密钥等。
详细配置可见: http://docs.ansible.com/ansible/latest/intro_inventory.html
配置完 hosts 文件之后便可以进行 ping:
$ ansible -i hosts all -m ping
10.8.0.164 | SUCCESS => {
"changed": false,
"ping": "pong"
}
150.95.155.202 | SUCCESS => {
"changed": false,
"ping": "pong"
}
也可以对某一组的主机进行 ping:
$ ansible -i hosts blog -m ping
150.95.155.202 | SUCCESS => {
"changed": false,
"ping": "pong"
}
执行
当能够连接上主机之后,便能对某个或者某组主机执行命令了:
$ ansible -i hosts local -m service -a "name=docker state=restarted" --become --ask-sudo-pass
SUDO password:
10.8.0.164 | SUCCESS => {
"changed": true,
"name": "docker",
"state": "started"
}
其中 -m
是选择使用的模块, ansible 有大量的模块可以使用, 不同的模块负责管理不同的功能,对 ansible 的学习也就是对这些模块熟悉的过程。
--become
是作为某个用户来执行,如果添加整个参数,或默认尝试使用 root
, 如果需要密码的话,还需要输入密码相关的参数, 权限相关的参数见:
Privilege Escalation Options:
control how and which user you become as on target hosts
-s, --sudo run operations with sudo (nopasswd) (deprecated, use
become)
-U SUDO_USER, --sudo-user=SUDO_USER
desired sudo user (default=root) (deprecated, use
become)
-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)
--ask-su-pass ask for su password (deprecated, use become)
-K, --ask-become-pass
ask for privilege escalation password
使用 root 用户:
Hypo-MBP:~ hypo$ ansible -i hosts local -a "whoami"
10.8.0.164 | SUCCESS | rc=0 >>
hypo
Hypo-MBP:~ hypo$ ansible -i hosts local -a "whoami" --become --ask-su-pass
SUDO password:
10.8.0.164 | SUCCESS | rc=0 >>
root
Playbook
通过命令行总归不是优雅的姿势,还是需要能存储的固定的脚本,每次部署的时候运行这个脚本,在 ansible 里便是 playbook。
NEXT 见:Ansible Playbook
Werkzeug 常用中间件
昨天记了手脚架 Werkzeug 的几个小工具( Werkzeug 的功能函数 ),今天再补几个中间件。之前在 HackerProxy (原gateway)中,便是通过自定义了一堆 Middleware,来把用户验证等常用逻辑完成的。第一次看到这种俄罗斯套娃式的代码我是震惊的,不过现在认为 Middleware 的确可以适当做一些比较轻的处理。
SharedDataMiddleware
SharedDataMiddleware 是提供一个静态文件分享(下载)的路由。和 flask 中默认的 static 不同,flask 是利用的 send_file
,而 SharedDataMiddleware 可以直接在 app 里注册相关的路由,并绑定一个磁盘路径,并分享这个路径下的文件。
import os
from werkzeug.wsgi import SharedDataMiddleware
app = SharedDataMiddleware(app, {
'/shared': os.path.join(os.path.dirname(__file__), 'shared')
})
ProfilerMiddleware
ProfilerMiddleware 是一个查看性能的中间件,它会在 profile_dir
下写入访问页面的程序运行状况,包括执行了那些函数,以及运行时间等。
from werkzeug.contrib.profiler import ProfilerMiddleware
app.wsgi_app = ProfilerMiddleware(app, profile_dir="/tmp")
但是如果使用 flask-debugtoolbar 这个拓展,便可以在 Web 上直接查看结果,因此不太建议直接用这个中间件。
DispacherMiddleware
DispatcherMiddleware 是个蛮好玩的中间件,他可以向一个 app 注册其他 app:
from werkzeug.wsgi import DispatcherMiddleware
app.wsgi_app = DispatcherMiddleware(app, {
'/app2': app2,
'/app3': app3
})
这时,访问 /app2 前缀的 url 就会使用 app2 的相关逻辑。如果对于一个程序可能有分开的需求,可以利用 DispacherMiddleware 做一些插拔操作。
Werkzeug 的功能函数
之前在 v2ex 上看到,有人问什么是手脚架,有人举例子,在 Python 中 Werkzeug 便像手脚架,而 flask 就像毛坯房。以此形容 Werkzeug 中有一堆还不错的工具。当然,如果在 flask 中使用 Werkzeug 的话,中间件更是个好东西:Werkzeug 常用中间件
数据结构
Werkzeug 内置里几个常用的数据结构,如果有相关需求就不用重复造轮子了。
TypeConversionDict
如字面意,这是一个类型转换字典,该字典提供一个 get 方法,接受三个参数,key, default, type。
>>> from werkzeug.datastructures import TypeConversionDict
>>> d = TypeConversionDict(foo='42', bar='blub')
>>> d.get('foo', type=int)
42
>>> d.get('bar', -1, type=int)
-1
ImmutableTypeConversionDict
ImmutableTypeConversionDict,便是 TypeConversionDict 的不可变版。
>>> from werkzeug.datastructures import ImmutableTypeConversionDict
>>> dic = ImmutableTypeConversionDict(key="value")
>>> dic.get("key")
'value'
>>> dic["key"] = "v2"
Traceback (most recent call last):
File "<input>", line 1, in <module>
dic["k2"] = "v2"
File "/Library/Python/2.7/site-packages/werkzeug/datastructures.py", line 182, in __setitem__
is_immutable(self)
File "/Library/Python/2.7/site-packages/werkzeug/datastructures.py", line 28, in is_immutable
raise TypeError('%r objects are immutable' % self.__class__.__name__)
TypeError: 'ImmutableTypeConversionDict' objects are immutable
MultiDict
MultiDict 这个东西在我第一次看到时候并没有理解这是做什么的,这是一个允许一个 key 有多个 value 的字典,但是如果当做普通字典用时,只会返回第一个值。
>>> d = MultiDict([('a', 'b'), ('a', 'c')])
>>> d
MultiDict([('a', 'b'), ('a', 'c')])
>>> d['a']
'b'
>>> d.getlist('a')
['b', 'c']
>>> 'a' in d
True
ImmutableMultiDict
ImmutableMultiDict 就是 MultiDict 的不可变版,在 flask 中的 request.args 便是一个 ImmutableMultiDict。
OrderedMultiDict
如字面意,OrderedMultiDict 是 MultiDict 中的 key 按字典序的版。
ImmutableOrderedMultiDict
同上,ImmutableOrderedMultiDict 为 OrderedMultiDict 不可变版。
功能函数
在 werkzeug.utils
下有一堆好玩的工具,可以在使用 flask 的时候直接拿来用。
cached_property
在 werkzeug.utils
中有个 cached_property
可以作为装饰器的类,这个装饰器和 @property
有相同的效果,不过被装饰的函数只会第一次运行,然后后面只会返回被缓存的结果。
In [17]: class Foo(object):
...:
...: @cached_property
...: def foo(self):
...: print("hello word")
...: return "hello"
...:
In [18]: obj = Foo()
In [19]: obj.foo
hello word
Out[19]: 'hello'
In [20]: obj.foo
Out[20]: 'hello'
import_string
import_string 可以通过字符串导出需要导入的模块:
In [21]: from werkzeug.utils import import_string
In [22]: import_string("flask")
Out[22]: <module 'flask' from '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/flask/__init__.py'>
In [23]: import_string("flask.Flask")
Out[23]: flask.app.Flask
secure_filename
可以用来生成一个合法的文件名:
>>> secure_filename("My cool movie.mov")
'My_cool_movie.mov'
>>> secure_filename("../../../etc/passwd")
'etc_passwd'
>>> secure_filename(u'i contain cool \xfcml\xe4uts.txt')
'i_contain_cool_umlauts.txt'
密码加密
之前在学校写东西,每次遇到密码加密,都是自己加各种盐,然后找个 md5 的库,加上几遍,对于密码的处理一直有 java 的风范。
Werkzeug 里面是有一套加密和密码验证的工具的。在 werkzeug.security
中,有 generate_password_hash
, check_password_hash
, 一对好用的工具。
generate_password_hash 接受三个参数:明文密码,加密方法(method, 默认为’pbkdf2:sha256’),盐的长度(salt_length,默认为8)。
In [25]: generate_password_hash("password")
Out[25]: 'pbkdf2:sha256:50000$F6gTN2Eh$52e209ed4431f9268d1bf16295439c46b25ab306acff72615837bdf268fee361'
In [26]: generate_password_hash("password")
Out[26]: 'pbkdf2:sha256:50000$GWo5sU55$729f84eb83e02549312fc4fc51db0614cbc783710902006dc510ccdac0a2b937'
返回的密文是:method$salt$hash
格式。并且,因为有盐的存在,所以同一个密码,并不会有相同的结果。
对于密码的验证:
In [27]: p = generate_password_hash("password")
In [29]: check_password_hash(p, "password")
Out[29]: True
In [30]: check_password_hash(p, "password1")
Out[30]: False
归并排序
导论第二章再讲时间复杂度的计算,通过写插入排序( 插入排序与循环不变式 )讲了循环的时间复杂度的计算,而接着通过归并排序介绍分治法以及分治法的时间复杂度。
A = [2, 5, 1, 3, 7, 6]
def merge_sort(nums, start, end):
if end <= start + 1:
return nums
m = (start + end ) / 2
merge_sort(nums, start, m)
merge_sort(nums, m, end) # not m + 1
l_nums = nums[start:m]
r_nums = nums[m:end]
l_nums_len = len(l_nums)
r_nums_len = len(r_nums)
s = j = 0
for i in range(start, end):
if s < l_nums_len and j < r_nums_len:
if l_nums[s] < r_nums[j]:
nums[i] = l_nums[s]
s += 1
else:
nums[i] = r_nums[j]
j += 1
else:
break
while i < end:
if s < l_nums_len:
nums[i] = l_nums[s]
s += 1
if j < r_nums_len:
nums[i] = r_nums[j]
j += 1
i += 1
return nums
我实现的代码可能不是很 pythonic,甚至也和导论中给出的伪代码有些区别。
在这里踩得坑是注释的地方,在我执行完 merge_sort(nums, start, m)
之后,想当然的执行了 merge_sort(nums, m + 1, end)
,其实这是不对的,因为这里的区间是 [start, end),如果在运行左边部分的时候,使用 m + 1
,那么在 m 那个位置上的值将被忽略掉。
Copyright © 2020 Powered by MWeb, Theme used GitHub CSS.