gitlab

[TOC] GitLab GitLab是用Ruby开发的完全免费的开源软件,按照 MIT 许可证分发的Git仓库管理工具,且具有wiki和issue跟踪功能。特别适合公司搭建自己的Git仓库,可局域网使用。 install 如果不是特大公司,千人以上的开发人员。中小型企业直接使用docker运行gitlab基本是足够的了,公司使用两三年了,基本没有遇到什么问题。 运行示例: docker pull gitlab-ce:9.5.10-ce.0 docker run --detach \ --hostname git.domain.com \ --env GITLAB_OMNIBUS_CONFIG="external_url 'https://git.domain.com/';" \ --env GITLAB_HOST="git.domain.com" \ --publish 80:80 \ --publish 2289:22 \ --name gitlab \ --restart always \ --volume /gitlab/config:/etc/gitlab \ --volume /gitlab/logs:/var/log/gitlab \ --volume /gitlab/data:/var/opt/gitlab \ --log-opt max-size=100m --log-opt max-file=10 \ gitlab-ce:9.5.10-ce.0 backup 备份很简单,itlab-rake gitlab:backup:create 即可搞定,备份以tar 压缩包保存。格式如下: 1545649962_2017_12_24_9.5.10_gitlab_backup.tar ,中间有备份时间和gitlab版本。docker备份: docker exec -t gitlab gitlab-rake gitlab:backup:create ls /gitlab/data/backups/1545649962_2017_12_24_9.5.10_gitlab_backup.tar restore 恢复必须在相同的gitlab版本上面,比如上面的例子是在 9.5.10 ...

July 12, 2016

运维自动化工具Ansible

[TOC] 一、Ansible概述 Ansible是今年来越来越火的一款开源运维自动化工具,通过Ansible可以实现运维自动化,提高运维工程师的工作效率,减少人为失误。Ansible通过本身集成的非常丰富的模块可以实现各种管理任务,其自带模块超过上千个。更为重要的是,它操作非常简单,即使小白也可以轻松上手,但它提供的功能又非常丰富,在运维领域,几乎可以做任何事。 1、Ansible特点 Ansible自2012年发布以来,很快在全球流行,其特点如下: Ansible基于Python开发,运维工程师对其二次开发相对比较容易; Ansible丰富的内置模块,几乎可以满足一切要求; 管理模式非常简单,一条命令可以影响上千台主机; 无客户端模式,底层通过SSH通信; Ansible发布后,也陆续被AWS、Google Cloud Platform、Microsoft Azure、Cisco、HP、VMware、Twitter等大公司接纳并投入使用; 二、Ansible的角色 使用者:如何使用Ansible实现自动化运维? Ansible工具集:Ansible可以实现的功能? 作用对象:Ansible可以影响哪些主机? 1、使用者 如下图所示:Ansible使用者可以采用多种方式和Ansible交互,图中展示了四种方式: CMDB:CMDB存储和管理者企业IT架构中的各项配置信息,是构建ITIL项目的核心工具,运维人员可以组合CMDB和Ansible,通过CMDB直接下发指令调用Ansible工具集完成操作者所希望达到的目标; PUBLIC/PRIVATE方式:Ansible除了丰富的内置模块外,同时还提供丰富的API语言接口,如PHP、Python、PERL等多种流行语言,基于PUBLIC/PRIVATE,Ansible以API调用的方式运行; Ad-Hoc命令集:Users直接通过Ad-Hoc命令集调用Ansible工具集来完成任务; Playbooks:Users预先编写好Ansible Playbooks,通过执行 Playbooks中预先编排好的任务集,按序执行任务; 2、Ansible工具集 Ansible工具集包含Inventory、Modules、Plugins和API。其中: Inventory:用来管理设备列表,可以通过分组实现,对组的调用直接影响组内的所有主机; Modules:是各种执行模块,几乎所有的管理任务都是通过模块执行的; Plugins:提供了各种附加功能; API:为编程人员提供一个接口,可以基于此做Ansible的二次开发; 具体表现如下: Ansible Playbooks:任务脚本,编排定义Ansible任务及的配置文件,由Ansible按序依次执行,通常是JSON格式的YML文件; Inventory:Ansible管理主机清单; Modules:Ansible执行命令功能模块,多数为内置的核心模块,也可自定义; Plugins:模块功能的补充,如连接类型插件、循环插件、变量插件、过滤插件等,该功能不太常用; API:供第三方程序调用的应用程序编程接口; Ansible:该部分图中表现得不太明显,组合Inventory、API、Modules、Plugins可以理解为是Ansible命令工具,其为核心执行工具; 3、作用对象 Ansible的作用对象不仅仅是Linux和非Linux操作系统的主机,也可以作用于各类PUBLIC/PRIVATE、商业和非商业设备的网络设施。 使用者使用Ansible或Ansible-Playbooks时,在服务器终端输入Ansible的Ad-Hoc命令集或Playbooks后,Ansible会遵循预选安排的规则将Playbooks逐步拆解为Play,再将Play组织成Ansible可以识别的任务,随后调用任务涉及的所有模块和插件,根据Inventory中定义的主机列表通过SSH将任务集以临时文件或命令的形式传输到远程客户端执行并返回执行结果,如果是临时文件则执行完毕后自动删除。 三、Ansible的配置 1、Ansible安装 Ansible的安装部署非常简单,以RPM安装为例,其依赖软件只有Python和SSH,且系统默认均已安装。Ansible的管理端只能是Linux,如Redhat、Debian、Centos。 1)通过YUM安装Ansible 可以自行从互联网上直接下载Ansible所需软件包 [root@centos01 ~]# cd /mnt/ansiblerepo/ansiblerepo/repodata/ [root@centos01 ansiblerepo]# vim /etc/yum.repos.d/local.repo [local] name=centos baseurl=file:///mnt/ansiblerepo/ansiblerepo <!--修改yum路径--> enabled=1 gpgcheck=0 [root@centos01 ~]# yum -y install ansible <!--安装Ansible自动化运维工具--> 2)验证安装结果 [root@centos01 ~]# ansible --version <!--如果命令可以正常执行,则表示Ansible工具安装成功--> ansible 2.3.1.0 config file = /etc/ansible/ansible.cfg configured module search path = Default w/o overrides python version = 2.7.5 (default, Nov 6 2016, 00:28:07) [GCC 4.8.5 20150623 (Red Hat 4.8.5-11)] 3)创建SSH免交互登录 Ansible通过SSH对设备进行管理,而SSH包含两种认证方式:一种是通过密码认证,另一种是通过密钥对验证。前者必须和系统交互,而后者是免交互登录。如果希望通过Ansible自动管理设备,应该配置为免交互登录被管理设备。 ...

July 2, 2016

Python异步aiohttp

[TOC] asyncio可以实现单线程并发IO操作。如果仅用在客户端,发挥的威力不大。如果把asyncio用在服务器端,例如Web服务器,由于HTTP连接就是IO操作,因此可以用单线程+coroutine实现多用户的高并发支持。 asyncio实现了TCP、UDP、SSL等协议,aiohttp则是基于asyncio实现的HTTP框架。 我们先安装aiohttp: pip install aiohttp 然后编写一个HTTP服务器,分别处理以下URL: / - 首页返回b'<h1>Index</h1>'; /hello/{name} - 根据URL参数返回文本hello, %s!。 代码如下: import asyncio from aiohttp import web async def index(request): await asyncio.sleep(0.5) return web.Response(body=b'<h1>Index</h1>') async def hello(request): await asyncio.sleep(0.5) text = '<h1>hello, %s!</h1>' % request.match_info['name'] return web.Response(body=text.encode('utf-8')) async def init(loop): app = web.Application(loop=loop) app.router.add_route('GET', '/', index) app.router.add_route('GET', '/hello/{name}', hello) srv = await loop.create_server(app.make_handler(), '127.0.0.1', 8000) print('Server started at http://127.0.0.1:8000...') return srv loop = asyncio.get_event_loop() loop.run_until_complete(init(loop)) loop.run_forever() 注意aiohttp的初始化函数init()也是一个coroutine,loop.create_server()则利用asyncio创建TCP服务。

July 1, 2016

Python异步async await

[TOC] 用asyncio提供的@asyncio.coroutine可以把一个generator标记为coroutine类型,然后在coroutine内部用yield from调用另一个coroutine实现异步操作。 为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法async和await,可以让coroutine的代码更简洁易读。 请注意,async和await是针对coroutine的新语法,要使用新的语法,只需要做两步简单的替换: 把@asyncio.coroutine替换为async; 把yield from替换为await。 让我们对比一下上一节的代码: @asyncio.coroutine def hello(): print("Hello world!") r = yield from asyncio.sleep(1) print("Hello again!") 用新语法重新编写如下: async def hello(): print("Hello world!") r = await asyncio.sleep(1) print("Hello again!") 剩下的代码保持不变。 小结 Python从3.5版本开始为asyncio提供了async和await的新语法; 注意新语法只能用在Python 3.5以及后续版本,如果使用3.4版本,则仍需使用上一节的方案。 try it ? 使用async异步获取sina、sohu和163的网站首页。 import asyncio async def wget(host): print('wget %s...' % host) connect = asyncio.open_connection(host, 80) [reader, writer] = await connect header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host writer.write(header.encode('utf-8')) await writer.drain() while True: line = await reader.readline() if line == b'\r\n': break print('%s header > %s' % (host, line.decode('utf-8').rstrip())) # Ignore the body, close the socket writer.close() if __name__ == '__main__': loop = asyncio.get_event_loop() tasks = [wget(host) for host in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']] loop.run_until_complete(asyncio.wait(tasks)) loop.close()

July 1, 2016

Python异步asyncio

[TOC] asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。 asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。 用asyncio实现Hello world代码如下: import asyncio @asyncio.coroutine def hello(): print("Hello world!") # 异步调用asyncio.sleep(1): r = yield from asyncio.sleep(1) print("Hello again!") # 获取EventLoop: loop = asyncio.get_event_loop() # 执行coroutine loop.run_until_complete(hello()) loop.close() @asyncio.coroutine把一个generator标记为coroutine类型,然后,我们就把这个coroutine扔到EventLoop中执行。 hello()会首先打印出Hello world!,然后,yield from语法可以让我们方便地调用另一个generator。由于asyncio.sleep()也是一个coroutine,所以线程不会等待asyncio.sleep(),而是直接中断并执行下一个消息循环。当asyncio.sleep()返回时,线程就可以从yield from拿到返回值(此处是None),然后接着执行下一行语句。 把asyncio.sleep(1)看成是一个耗时1秒的IO操作,在此期间,主线程并未等待,而是去执行EventLoop中其他可以执行的coroutine了,因此可以实现并发执行。 我们用Task封装两个coroutine试试: import threading import asyncio @asyncio.coroutine def hello(): print('Hello world! (%s)' % threading.currentThread()) yield from asyncio.sleep(1) print('Hello again! (%s)' % threading.currentThread()) loop = asyncio.get_event_loop() tasks = [hello(), hello()] loop.run_until_complete(asyncio.wait(tasks)) loop.close() 观察执行过程: ...

July 1, 2016

Python协程

[TOC] 协程,又称微线程。英文名Coroutine。 协程的概念很早就提出来了,但直到最近几年才在某些语言(如Lua)中得到广泛应用。 子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。 所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。 子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。 协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。 注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断。比如子程序A、B: def A(): print('1') print('2') print('3') def B(): print('x') print('y') print('z') 假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果可能是: 1 2 x y 3 z 但是在A中是没有调用B的,所以协程的调用比函数调用理解起来要难一些。 看起来A、B的执行有点像多线程,但协程的特点在于是一个线程执行,那和多线程比,协程有何优势? 最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。 第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。 因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。 Python对协程的支持是通过generator实现的。 在generator中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值。 但是Python的yield不但可以返回一个值,它还可以接收调用者发出的参数。 来看例子: 传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。 如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高: def consumer(): r = '' while True: n = yield r if not n: return print('[CONSUMER] Consuming %s...' % n) r = '200 OK' def produce(c): c.send(None) n = 0 while n < 5: n = n + 1 print('[PRODUCER] Producing %s...' % n) r = c.send(n) print('[PRODUCER] Consumer return: %s' % r) c.close() c = consumer() produce(c) 执行结果: ...

June 30, 2016

Python异步IO

[TOC] 在IO编程一节中,我们已经知道,CPU的速度远远快于磁盘、网络等IO。在一个线程中,CPU执行代码的速度极快,然而,一旦遇到IO操作,如读写文件、发送网络数据时,就需要等待IO操作完成,才能继续进行下一步操作。这种情况称为同步IO。 在IO操作的过程中,当前线程被挂起,而其他需要CPU执行的代码就无法被当前线程执行了。 因为一个IO操作就阻塞了当前线程,导致其他代码无法执行,所以我们必须使用多线程或者多进程来并发执行代码,为多个用户服务。每个用户都会分配一个线程,如果遇到IO导致线程被挂起,其他用户的线程不受影响。 多线程和多进程的模型虽然解决了并发问题,但是系统不能无上限地增加线程。由于系统切换线程的开销也很大,所以,一旦线程数量过多,CPU的时间就花在线程切换上了,真正运行代码的时间就少了,结果导致性能严重下降。 由于我们要解决的问题是CPU高速执行能力和IO设备的龟速严重不匹配,多线程和多进程只是解决这一问题的一种方法。 另一种解决IO问题的方法是异步IO。当代码需要执行一个耗时的IO操作时,它只发出IO指令,并不等待IO结果,然后就去执行其他代码了。一段时间后,当IO返回结果时,再通知CPU进行处理。 可以想象如果按普通顺序写出的代码实际上是没法完成异步IO的,所以,同步IO模型的代码是无法实现异步IO模型的。 异步IO模型需要一个消息循环,在消息循环中,主线程不断地重复“读取消息-处理消息”这一过程: loop = get_event_loop() while True: event = loop.get_event() process_event(event) 消息模型其实早在应用在桌面应用程序中了。一个GUI程序的主线程就负责不停地读取消息并处理消息。所有的键盘、鼠标等消息都被发送到GUI程序的消息队列中,然后由GUI程序的主线程处理。 由于GUI线程处理键盘、鼠标等消息的速度非常快,所以用户感觉不到延迟。某些时候,GUI线程在一个消息处理的过程中遇到问题导致一次消息处理时间过长,此时,用户会感觉到整个GUI程序停止响应了,敲键盘、点鼠标都没有反应。这种情况说明在消息模型中,处理一个消息必须非常迅速,否则,主线程将无法及时处理消息队列中的其他消息,导致程序看上去停止响应。 消息模型是如何解决同步IO必须等待IO操作这一问题的呢?当遇到IO操作时,代码只负责发出IO请求,不等待IO结果,然后直接结束本轮消息处理,进入下一轮消息处理过程。当IO操作完成后,将收到一条“IO完成”的消息,处理该消息时就可以直接获取IO操作结果。 在“发出IO请求”到收到“IO完成”的这段时间里,同步IO模型下,主线程只能挂起,但异步IO模型下,主线程并没有休息,而是在消息循环中继续处理其他消息。这样,在异步IO模型下,一个线程就可以同时处理多个IO请求,并且没有切换线程的操作。对于大多数IO密集型的应用程序,使用异步IO将大大提升系统的多任务处理能力。

June 29, 2016

Python网络编程TCP

[TOC] Socket是网络编程的一个抽象概念。通常我们用一个Socket表示“打开了一个网络链接”,而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可。 客户端 大多数连接都是可靠的TCP连接。创建TCP连接时,主动发起连接的叫客户端,被动响应连接的叫服务器。 举个例子,当我们在浏览器中访问新浪时,我们自己的计算机就是客户端,浏览器会主动向新浪的服务器发起连接。如果一切顺利,新浪的服务器接受了我们的连接,一个TCP连接就建立起来的,后面的通信就是发送网页内容了。 所以,我们要创建一个基于TCP连接的Socket,可以这样做: # 导入socket库: import socket # 创建一个socket: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 建立连接: s.connect(('www.sina.com.cn', 80)) 创建Socket时,AF_INET指定使用IPv4协议,如果要用更先进的IPv6,就指定为AF_INET6。SOCK_STREAM指定使用面向流的TCP协议,这样,一个Socket对象就创建成功,但是还没有建立连接。 客户端要主动发起TCP连接,必须知道服务器的IP地址和端口号。新浪网站的IP地址可以用域名www.sina.com.cn自动转换到IP地址,但是怎么知道新浪服务器的端口号呢? 答案是作为服务器,提供什么样的服务,端口号就必须固定下来。由于我们想要访问网页,因此新浪提供网页服务的服务器必须把端口号固定在80端口,因为80端口是Web服务的标准端口。其他服务都有对应的标准端口号,例如SMTP服务是25端口,FTP服务是21端口,等等。端口号小于1024的是Internet标准服务的端口,端口号大于1024的,可以任意使用。 因此,我们连接新浪服务器的代码如下: s.connect(('www.sina.com.cn', 80)) 注意参数是一个tuple,包含地址和端口号。 建立TCP连接后,我们就可以向新浪服务器发送请求,要求返回首页的内容: # 发送数据: s.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n') TCP连接创建的是双向通道,双方都可以同时给对方发数据。但是谁先发谁后发,怎么协调,要根据具体的协议来决定。例如,HTTP协议规定客户端必须先发请求给服务器,服务器收到后才发数据给客户端。 发送的文本格式必须符合HTTP标准,如果格式没问题,接下来就可以接收新浪服务器返回的数据了: # 接收数据: buffer = [] while True: # 每次最多接收1k字节: d = s.recv(1024) if d: buffer.append(d) else: break data = b''.join(buffer) 接收数据时,调用recv(max)方法,一次最多接收指定的字节数,因此,在一个while循环中反复接收,直到recv()返回空数据,表示接收完毕,退出循环。 当我们接收完数据后,调用close()方法关闭Socket,这样,一次完整的网络通信就结束了: # 关闭连接: s.close() 接收到的数据包括HTTP头和网页本身,我们只需要把HTTP头和网页分离一下,把HTTP头打印出来,网页内容保存到文件: header, html = data.split(b'\r\n\r\n', 1) print(header.decode('utf-8')) # 把接收的数据写入文件: with open('sina.html', 'wb') as f: f.write(html) 现在,只需要在浏览器中打开这个sina.html文件,就可以看到新浪的首页了。 ...

June 28, 2016

Python网络编程UDP

[TOC] TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据。相对TCP,UDP则是面向无连接的协议。 使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。 虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。 我们来看看如何通过UDP协议传输数据。和TCP类似,使用UDP的通信双方也分为客户端和服务器。服务器首先需要绑定端口: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 绑定端口: s.bind(('127.0.0.1', 9999)) 创建Socket时,SOCK_DGRAM指定了这个Socket的类型是UDP。绑定端口和TCP一样,但是不需要调用listen()方法,而是直接接收来自任何客户端的数据: print('Bind UDP on 9999...') while True: # 接收数据: data, addr = s.recvfrom(1024) print('Received from %s:%s.' % (addr, data)) s.sendto(b'Hello, %s!' % data, addr) recvfrom()方法返回数据和客户端的地址与端口,这样,服务器收到数据后,直接调用sendto()就可以把数据用UDP发给客户端。 注意这里省掉了多线程,因为这个例子很简单。 客户端使用UDP时,首先仍然创建基于UDP的Socket,然后,不需要调用connect(),直接通过sendto()给服务器发数据: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) for data in [b'Superman', b'Hooby', b'Huyu']: # 发送数据: s.sendto(data, ('127.0.0.1', 9999)) # 接收数据: print(s.recv(1024).decode('utf-8')) s.close() 从服务器接收数据仍然调用recv()方法。 仍然用两个命令行分别启动服务器和客户端测试,结果如下: server: Bind UDP on 9999... Received from ('127.0.0.1', 58531):b'Superman'. Received from ('127.0.0.1', 58531):b'Hooby'. Received from ('127.0.0.1', 58531):b'Huyu'. client: ...

June 28, 2016

Python环境之virtualenv

[TOC] 在开发Python应用程序的时候,系统安装的Python3只有一个版本:3.4。所有第三方的包都会被pip安装到Python3的site-packages目录下。 如果我们要同时开发多个应用程序,那这些应用程序都会共用一个Python,就是安装在系统的Python 3。如果应用A需要jinja 2.7,而应用B需要jinja 2.6怎么办? 这种情况下,每个应用可能需要各自拥有一套“独立”的Python运行环境。virtualenv就是用来为一个应用创建一套“隔离”的Python运行环境。 首先,我们用pip安装virtualenv: $ pip3 install virtualenv 然后,假定我们要开发一个新的项目,需要一套独立的Python运行环境,可以这么做: 第一步,创建目录: Mac:~ michael$ mkdir myproject Mac:~ michael$ cd myproject/ Mac:myproject michael$ 第二步,创建一个独立的Python运行环境,命名为venv: Mac:myproject michael$ virtualenv --no-site-packages venv Using base prefix '/usr/local/.../Python.framework/Versions/3.4' New python executable in venv/bin/python3.4 Also creating executable in venv/bin/python Installing setuptools, pip, wheel...done. 命令virtualenv就可以创建一个独立的Python运行环境,我们还加上了参数--no-site-packages,这样,已经安装到系统Python环境中的所有第三方包都不会复制过来,这样,我们就得到了一个不带任何第三方包的“干净”的Python运行环境。 新建的Python环境被放到当前目录下的venv目录。有了venv这个Python环境,可以用source进入该环境: Mac:myproject michael$ source venv/bin/activate (venv)Mac:myproject michael$ 注意到命令提示符变了,有个(venv)前缀,表示当前环境是一个名为venv的Python环境。 下面正常安装各种第三方包,并运行python命令: (venv)Mac:myproject michael$ pip install jinja2 ... Successfully installed jinja2-2.7.3 markupsafe-0.23 (venv)Mac:myproject michael$ python myapp.py ... 在venv环境下,用pip安装的包都被安装到venv这个环境下,系统Python环境不受任何影响。也就是说,venv环境是专门针对myproject这个应用创建的。 ...

June 27, 2016