├── _Sidebar.md ├── .gitignore ├── readme.md ├── asset ├── proxmark1.jpg ├── py3_coro_1.png ├── rpc_call.png ├── rpc_cast.png ├── menu_push_1.png ├── menu_push_2.png ├── menu_push_3.png ├── menu_push_5.png ├── zuul_status.png ├── menu_push_4_1.png ├── menu_push_4_2.png ├── menu_push_4_3.png ├── CI_workflow_netease.png ├── under_construction.gif ├── CI_workflow_community.png ├── dpdk-gw-performance_0.png ├── dpdk-gw-performance_1.png ├── dpdk-gw-performance_2.png └── dpdk-gw-performance_3.png ├── home.md ├── LICENSE.txt ├── debian_package ├── build-debian-openstack-package.md ├── build-debian-kernel-package.md └── new_way_to_build_python_debian_package.md ├── misc ├── pal4-essays.md └── Yann-Andrea-Steiner-reading-notes.rst ├── markup ├── tex-examples.md ├── markdown-guide.md ├── asciidoc-guide.asciidoc └── rst-guide.rst ├── network ├── dpdk-gw-performance.md ├── how-tcpkill-works.md ├── bgp-on-vpn.md └── openssl-self-signed-certs-cheatsheet.md ├── openstack ├── nova-network-rpc-registry.md ├── inside-openstack-rpc.md ├── nova-network-api.md ├── inside-openstack-iptables.md ├── ci-migration-summary.md ├── setup-neutron-dev-env-from-src.md ├── setup-devstack.md ├── python-packaging.md ├── inside-nova-periodic-task.md ├── inside-eventlet-concurrency.md ├── setup-log-collect-service.md ├── nova-report-state-timed-out-on-dev11-analysis.md ├── setup-ci-system.md └── setup-openstack-local-development-environment.md ├── windows └── essentials_win.md ├── macos └── essentials_osx.md ├── programming └── python_3_coroutines.md └── rfid └── hacking-ic-card-with-proxmark3.md /_Sidebar.md: -------------------------------------------------------------------------------- 1 | [[_TOC_]] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | nohup.out 2 | work 3 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | stanzgy's personal article archive 2 | -------------------------------------------------------------------------------- /asset/proxmark1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanzgy/wiki/HEAD/asset/proxmark1.jpg -------------------------------------------------------------------------------- /asset/py3_coro_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanzgy/wiki/HEAD/asset/py3_coro_1.png -------------------------------------------------------------------------------- /asset/rpc_call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanzgy/wiki/HEAD/asset/rpc_call.png -------------------------------------------------------------------------------- /asset/rpc_cast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanzgy/wiki/HEAD/asset/rpc_cast.png -------------------------------------------------------------------------------- /asset/menu_push_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanzgy/wiki/HEAD/asset/menu_push_1.png -------------------------------------------------------------------------------- /asset/menu_push_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanzgy/wiki/HEAD/asset/menu_push_2.png -------------------------------------------------------------------------------- /asset/menu_push_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanzgy/wiki/HEAD/asset/menu_push_3.png -------------------------------------------------------------------------------- /asset/menu_push_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanzgy/wiki/HEAD/asset/menu_push_5.png -------------------------------------------------------------------------------- /asset/zuul_status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanzgy/wiki/HEAD/asset/zuul_status.png -------------------------------------------------------------------------------- /asset/menu_push_4_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanzgy/wiki/HEAD/asset/menu_push_4_1.png -------------------------------------------------------------------------------- /asset/menu_push_4_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanzgy/wiki/HEAD/asset/menu_push_4_2.png -------------------------------------------------------------------------------- /asset/menu_push_4_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanzgy/wiki/HEAD/asset/menu_push_4_3.png -------------------------------------------------------------------------------- /asset/CI_workflow_netease.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanzgy/wiki/HEAD/asset/CI_workflow_netease.png -------------------------------------------------------------------------------- /asset/under_construction.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanzgy/wiki/HEAD/asset/under_construction.gif -------------------------------------------------------------------------------- /asset/CI_workflow_community.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanzgy/wiki/HEAD/asset/CI_workflow_community.png -------------------------------------------------------------------------------- /asset/dpdk-gw-performance_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanzgy/wiki/HEAD/asset/dpdk-gw-performance_0.png -------------------------------------------------------------------------------- /asset/dpdk-gw-performance_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanzgy/wiki/HEAD/asset/dpdk-gw-performance_1.png -------------------------------------------------------------------------------- /asset/dpdk-gw-performance_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanzgy/wiki/HEAD/asset/dpdk-gw-performance_2.png -------------------------------------------------------------------------------- /asset/dpdk-gw-performance_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanzgy/wiki/HEAD/asset/dpdk-gw-performance_3.png -------------------------------------------------------------------------------- /home.md: -------------------------------------------------------------------------------- 1 | Please refer to [All pages](/pages) for details. 2 | 3 | maintained by stanzgy \ 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This repo is licensed under a 2 | Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 3 | 4 | You should have received a copy of the license along with this 5 | work. If not, see . 6 | -------------------------------------------------------------------------------- /debian_package/build-debian-openstack-package.md: -------------------------------------------------------------------------------- 1 | # Build debian package with git-buildpackage 2 | 3 | ## Get the source 4 | 5 | stanzgy % git clone ssh://SCM/openstack/nova.git 6 | 7 | ## Checkout the debian package 8 | 9 | stanzgy % cd nova 10 | 11 | stanzgy % git checkout -b debian/folsom origin/debian/folsom 12 | 13 | ## Commit your changes 14 | 15 | .... 16 | 17 | stanzgy % git commit -a 18 | 19 | ## Add debian changelog 20 | 21 | stanzgy % git dch 22 | 23 | stanzgy % vi debian/changelog 24 | 25 | stanzgy % git commit -a -m 'build package' 26 | 27 | ## Build debian package with git-buildpackage 28 | 29 | stanzgy % git-buildpackage --git-dist=wheezy --git-arch=amd64 --git-pbuilder --git-cleaner=/bin/true -nc 30 | 31 | ## Reference 32 | 33 | More information about debian package building toolchains, please refer to 34 | 35 | * [Debian New Maintainers' Guide](http://www.debian.org/doc/manuals/maint-guide/) 36 | 37 | * [Debian Policy Manual](http://www.debian.org/doc/debian-policy/) 38 | -------------------------------------------------------------------------------- /debian_package/build-debian-kernel-package.md: -------------------------------------------------------------------------------- 1 | # Build Debian Kernel package 2 | 3 | ## Debootstrap to target environment 4 | 5 | # DIST=wheezy ARCH=amd64 git-pbuilder login 6 | 7 | ## Install essential tools 8 | 9 | # aptitude install fakeroot kernel-package build-essential 10 | 11 | ## Get kernel source 12 | 13 | # aptitude install linux-source-3.2 14 | 15 | # tar jxf /usr/src/linux-source-3.2.tar.bz2 16 | 17 | ## Apply your changes 18 | 19 | # cd linux-source-3.2 20 | 21 | # make menuconfig 22 | 23 | # patch ... 24 | 25 | ## Update package maintainer information 26 | 27 | # sed -i 's/^maintainer \:=.*//' /etc/kernel-pkg.conf 28 | 29 | # sed -i 's/^email \:=.*//' /etc/kernel-pkg.conf 30 | 31 | # echo "maintainer := Zhang Gengyuan" >> /etc/kernel-pkg.conf 32 | 33 | # echo "email := stan.zgy@gmail.com" >> /etc/kernel-pkg.conf 34 | 35 | ## Build kernel package 36 | 37 | # make-kpkg -j24 --rootcmd fakeroot --initrd --revision 3.2.41-2+netease1 38 | --append_to_version -openstack-amd64 kernel_image 39 | -------------------------------------------------------------------------------- /misc/pal4-essays.md: -------------------------------------------------------------------------------- 1 | ## 都是些小事 三 2 | 3 | Author: stanzgy 4 | 5 | Date: 2008-10-23 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 | -------------------------------------------------------------------------------- /markup/tex-examples.md: -------------------------------------------------------------------------------- 1 | ### The Lorenz Equations 2 | 3 | 4 | $$ 5 | \begin{aligned} 6 | \dot{x} & = \sigma(y-x) \newline 7 | \dot{y} & = \rho x - y - xz \newline 8 | \dot{z} & = -\beta z + xy 9 | \end{aligned} 10 | $$ 11 | 12 | 13 | ### The Cauchy-Schwarz Inequality 14 | 15 | $$\left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right) 16 | \left( \sum_{k=1}^n b_k^2 \right)$$ 17 | 18 | 19 | ### A Cross Product Formula 20 | 21 | $$ 22 | \mathbf{V}_1 \times \mathbf{V}_2 = \begin{vmatrix} 23 | \mathbf{i} & \mathbf{j} & \mathbf{k} \newline 24 | \frac{\partial X}{\partial u} & \frac{\partial Y}{\partial u} & 0 \newline 25 | \frac{\partial X}{\partial v} & \frac{\partial Y}{\partial v} & 0 26 | \end{vmatrix} 27 | $$ 28 | 29 | 30 | ### The probability of getting k heads when flipping n coins is 31 | 32 | $$ 33 | P(E) = {n \choose k} p^k (1-p)^{ n-k} 34 | $$ 35 | 36 | 37 | ### An Identity of Ramanujan 38 | 39 | $$ 40 | \frac{1}{\Bigl(\sqrt{\phi \sqrt{5}}-\phi\Bigr) e^{\frac25 \pi}} = 41 | 1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}} 42 | {1+\frac{e^{-8\pi}} {1+\ldots} } } } 43 | $$ 44 | 45 | 46 | ### A Rogers-Ramanujan Identity 47 | 48 | $$ 49 | 1 + \frac{q^2}{(1-q)}+\frac{q^6}{(1-q)(1-q^2)}+\cdots = 50 | \prod_{j=0}^{\infty}\frac{1}{(1-q^{5j+2})(1-q^{5j+3})}, 51 | \quad\quad \text{for $|q|<1$}. 52 | $$ 53 | 54 | 55 | ### Maxwell’s Equations 56 | 57 | $$ 58 | \begin{aligned} 59 | \nabla \times \vec{\mathbf{B}} -\, \frac1c\, 60 | \frac{\partial\vec{\mathbf{E}}}{\partial t} & = 61 | \frac{4\pi}{c}\vec{\mathbf{j}} \newline \nabla \cdot \vec{\mathbf{E}} & = 62 | 4 \pi \rho \newline \nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, 63 | \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \newline 64 | \nabla \cdot \vec{\mathbf{B}} & = 0 \end{aligned} 65 | $$ 66 | 67 | -------------------------------------------------------------------------------- /network/dpdk-gw-performance.md: -------------------------------------------------------------------------------- 1 | ## DPDK网关性能瓶颈分析 2 | 3 | ### 背景 4 | 5 | 我们线上环境部署的基于Open vSwitch的DPDK技术实现的专线网关,前段时间遇到了用户特定业务流量导致 6 | 网关性能异常下降的问题。普通情况下,根据我们自己的测试结果,单台网关使用Intel/Mellanox 25G网卡, 7 | 测试流量打固定端口,单向转发性能可以达到900万以上的PPS,随机端口流量也可以达到500~600万的PPS。 8 | 但是当用户特定的业务流量走网关时,网关的转发性能会骤降到120万PPS左右,影响用户的线上业务。 9 | 10 | 在问题发生时,我们可以在网关观察到 ovs pmd 统计结果的 megaflow hits 和处理每个报文的cpu cycle 11 | 偏高: 12 | 13 | ![dpdk-gw-0](../asset/dpdk-gw-performance_0.png) 14 | 15 | ### 问题重现 16 | 17 | 在问题出现后,我们迅速扩容了一批网关,第一时间先解决用户的性能需求。同时另外准备了一批测试机器, 18 | 尝试重现用户的问题。 19 | 通过准备的10台物理服务器和30台云主机,走网关模拟的用户的流量模型,经过各种尝试后,终于重现了 20 | 用户遇到的问题。 21 | 22 | 我们发现重现用户问题需要满足两个条件: 23 | 24 | * 大量TCP短连接 25 | * 网关处于高负载状态 26 | 27 | 我们在测试的机器上通过并发跑 netperf TCP_CRR 测试将网关打满,便可以重现这个场景。 28 | 29 | ### 问题分析 30 | 31 | 确认问题出现的场景后,我们团队提出了几个可能出现问题的地方: 32 | 33 | * Open vSwitch 的 EMC 缓存耗尽,报文转发走了效率更低的路径 34 | * Open vSwitch 的 socket-mem 内存资源耗尽,申请内存导致了性能下降 35 | * 网卡队列的CPU core绑定/NUMA绑定出现了错误 36 | * 网卡驱动的问题,等等 37 | 38 | 对于ovs emc缓存耗尽的猜测,我们通过升级ovs版本、开启smc缓存,问题仍然存在,排除可能。 39 | 对于ovs socket-mem 不够大的猜测,我们通过修改ovs配置大幅增加其大小,问题仍然存在,排除可能。 40 | 对于网卡队列的cpu/numa绑定出错的猜测,我们确认了没有使用CPU超线程core、处理单侧流量的网卡队列的 41 | pmd绑定在相同的numa节点上,排除可能。 42 | 对于网卡驱动可能有问题的猜测,我们测试了Intel和Mellanox的网卡,都存在相同的问题,也排除了可能。 43 | 44 | 猜测可能出现问题的地方都排除了可能,只能直接分析ovs的调用堆栈分析原因了。 45 | 46 | 首先我使用perf/flamegraph工具,绘制了60s ovs pmd线程的cpu火炬图。 47 | 48 | ![dpdk-gw-1](../asset/dpdk-gw-performance_1.png) 49 | 50 | 可以看到大量的CPU时间都消耗在了pthread等锁的函数调用上,但是从火炬图还是无法确认具体原因。 51 | 52 | 于是我又编写了一个调试脚本,循环调用gdb打印ovs pmd线程的调用堆栈,看看从结果能不能找出头绪, 53 | 终于有了新发现! 54 | 55 | ![dpdk-gw-2](../asset/dpdk-gw-performance_2.png) 56 | ![dpdk-gw-3](../asset/dpdk-gw-performance_3.png) 57 | 58 | 从我们脚本的统计结果可以看到,超过三分之一的ovs pmd线程都处于 meter_lock 这个锁函数上, 59 | 原因应该就是这里了,也就是ovs的meter功能导致的问题。 60 | 61 | 我们在测试网关上配置了跳过ovs meter的流表,网关性能瞬间恢复了正常。 62 | 63 | 由于线上的网关基本都是用户独占使用,关闭ovs meter(qos)功能并不会影响用户的实际使用, 64 | 为了快速解决线上的这个问题,我们迅速开发了跳过ovs meter的api开关以解决用户网关的这个问题。 65 | 66 | 对于ovs,可能是由于代码实现、性能优化不到位导致了这个问题,有待我们继续对ovs源码进行分析。 67 | -------------------------------------------------------------------------------- /markup/markdown-guide.md: -------------------------------------------------------------------------------- 1 | ## Phrase Emphasis ## 2 | 3 | *italic* **bold** 4 | _italic_ __bold__ 5 | 6 | 7 | ## Links ## 8 | 9 | Inline: 10 | 11 | An [example](http://url.com/ "Title") 12 | 13 | Reference-style labels (titles are optional): 14 | 15 | An [example][id]. Then, anywhere 16 | else in the doc, define the link: 17 | 18 | [id]: http://example.com/ "Title" 19 | 20 | 21 | ## Images ## 22 | 23 | Inline (titles are optional): 24 | 25 | ![alt text](/path/img.jpg "Title") 26 | 27 | Reference-style: 28 | 29 | ![alt text][id] 30 | 31 | [id]: /url/to/img.jpg "Title" 32 | 33 | 34 | ## Headers ## 35 | 36 | Setext-style: 37 | 38 | Header 1 39 | ======== 40 | 41 | Header 2 42 | -------- 43 | 44 | atx-style (closing #'s are optional): 45 | 46 | # Header 1 # 47 | 48 | ## Header 2 ## 49 | 50 | ###### Header 6 51 | 52 | 53 | ## Lists ## 54 | 55 | Ordered, without paragraphs: 56 | 57 | 1. Foo 58 | 2. Bar 59 | 60 | Unordered, with paragraphs: 61 | 62 | * A list item. 63 | 64 | With multiple paragraphs. 65 | 66 | * Bar 67 | 68 | You can nest them: 69 | 70 | * Abacus 71 | * answer 72 | * Bubbles 73 | 1. bunk 74 | 2. bupkis 75 | * BELITTLER 76 | 3. burper 77 | * Cunning 78 | 79 | 80 | ## Blockquotes ## 81 | 82 | > Email-style angle brackets 83 | > are used for blockquotes. 84 | 85 | > > And, they can be nested. 86 | 87 | > #### Headers in blockquotes 88 | > 89 | > * You can quote a list. 90 | > * Etc. 91 | 92 | 93 | ## Code Spans ## 94 | 95 | `` spans are delimited 96 | by backticks. 97 | 98 | You can include literal backticks 99 | like `` `this` ``. 100 | 101 | 102 | ## Preformatted Code Blocks ## 103 | 104 | Indent every line of a code block by at least 4 spaces or 1 tab. 105 | 106 | This is a normal paragraph. 107 | 108 | This is a preformatted 109 | code block. 110 | 111 | 112 | ## Horizontal Rules ## 113 | 114 | Three or more dashes or asterisks: 115 | 116 | --- 117 | 118 | * * * 119 | 120 | - - - - 121 | 122 | 123 | ## Manual Line Breaks ## 124 | 125 | End a line with two or more spaces: 126 | 127 | Roses are red, 128 | Violets are blue. 129 | -------------------------------------------------------------------------------- /openstack/nova-network-rpc-registry.md: -------------------------------------------------------------------------------- 1 | # Openstack network service registry procedure 2 | 3 | This page describes how nova-network register itself as a "network" service in rpc queues. 4 | In summary, all nova-network listen on two rpc message queues "`network`" and "`network.{host}`" to prepare to make a remote response. 5 | 6 | For more detailed rpc information, please refer to [[inside-openstack-rpc]]. 7 | 8 | ## how nova-network register itself in message queues 9 | 10 | ### NetworkManager call super class to register itself as "network" in `__init__()` 11 | > nova / nova / network / manager.py 12 | 13 | ```python 14 | class NetworkManager(manager.SchedulerDependentManager): 15 | ... 16 | def __init__(self, network_driver=None, *args, **kwargs): 17 | ... 18 | super(NetworkManager, self).__init__(service_name='network', 19 | *args, **kwargs) 20 | ``` 21 | 22 | ### `__init__()` in super class 23 | > nova / nova / manager.py 24 | 25 | note that periodic_task decorator made _publish_service_capabilities() run periodically to notify network service 26 | 27 | ```python 28 | class SchedulerDependentManager(Manager): 29 | """ Periodically send capability updates to the Scheduler services. 30 | 31 | Services that need to update the Scheduler of their capabilities 32 | should derive from this class. Otherwise they can derive from 33 | manager.Manager directly. Updates are only sent after 34 | update_service_capabilities is called with non-None values. 35 | 36 | """ 37 | 38 | def __init__(self, host=None, db_driver=None, service_name='undefined'): 39 | self.last_capabilities = None 40 | self.service_name = service_name 41 | super(SchedulerDependentManager, self).__init__(host, db_driver) 42 | 43 | def update_service_capabilities(self, capabilities): 44 | """Remember these capabilities to send on next periodic update.""" 45 | self.last_capabilities = capabilities 46 | 47 | @periodic_task 48 | def _publish_service_capabilities(self, context): 49 | """Pass data back to the scheduler at a periodic interval.""" 50 | if self.last_capabilities: 51 | LOG.debug(_('Notifying Schedulers of capabilities ...')) 52 | api.update_service_capabilities(context, self.service_name, 53 | self.host, self.last_capabilities) 54 | 55 | 56 | def periodic_task(*args, **kwargs): 57 | """ Decorator to indicate that a method is a periodic task. 58 | 59 | This decorator can be used in two ways: 60 | 61 | 1. Without arguments '@periodic_task', this will be run on every tick 62 | of the periodic scheduler. 63 | 64 | 2. With arguments, @periodic_task(ticks_between_runs=N), this will be 65 | run on every N ticks of the periodic scheduler. 66 | """ 67 | def decorator(f): 68 | f._periodic_task = True 69 | f._ticks_between_runs = kwargs.pop('ticks_between_runs', 0) 70 | return f 71 | ``` 72 | -------------------------------------------------------------------------------- /openstack/inside-openstack-rpc.md: -------------------------------------------------------------------------------- 1 | # Inside Openstack RPC 2 | 3 | > ![Under construction](/asset/under_construction.gif) 4 | > 5 | > `>>> page under construction <<<` 6 | 7 | This page briefly introduce openstack rpc's logic. 8 | 9 | For more detailed information please refer to [Office Document](http://docs.openstack.org/developer/nova/devref/rpc.html). 10 | 11 | ## RPC Calls 12 | 13 | The diagram below shows the message flow during an `rpc.call` operation: 14 | 15 | ![RPC Call](/asset/rpc_call.png) 16 | 17 | * a Topic Publisher is instantiated to send the message request to the queuing system; immediately before the publishing operation, a Direct Consumer is instantiated to wait for the response message. 18 | * once the message is dispatched by the exchange, it is fetched by the Topic Consumer dictated by the routing key (such as .topic.host.) and passed to the Worker in charge of the task. 19 | * once the task is completed, a Direct Publisher is allocated to send the response message to the queuing system. 20 | * once the message is dispatched by the exchange, it is fetched by the Direct Consumer dictated by the routing key (such as .msg_id.) and passed to the Invoker. 21 | 22 | 23 | ## RPC Casts 24 | 25 | The diagram below the message flow during an `rpc.cast` operation: 26 | 27 | ![RPC Cast](/asset/rpc_cast.png) 28 | 29 | * A Topic Publisher is instantiated to send the message request to the queuing system. 30 | * Once the message is dispatched by the exchange, it is fetched by the Topic Consumer dictated by the routing key (such as .topic.) and passed to the Worker in charge of the task. 31 | 32 | 33 | ## RPC implementation code in nova 34 | > nova / nova / rpc / amqp.py 35 | 36 | ```python 37 | def multicall(context, topic, msg, timeout, connection_pool): 38 | """Make a call that returns multiple times.""" 39 | # Can't use 'with' for multicall, as it returns an iterator 40 | # that will continue to use the connection. When it's done, 41 | # connection.close() will get called which will put it back into 42 | # the pool 43 | LOG.debug(_('Making asynchronous call on %s ...'), topic) 44 | msg_id = uuid.uuid4().hex 45 | msg.update({'_msg_id': msg_id}) 46 | LOG.debug(_('MSG_ID is %s') % (msg_id)) 47 | pack_context(msg, context) 48 | 49 | conn = ConnectionContext(connection_pool) 50 | wait_msg = MulticallWaiter(conn, timeout) 51 | conn.declare_direct_consumer(msg_id, wait_msg) 52 | conn.topic_send(topic, msg) 53 | return wait_msg 54 | 55 | 56 | def call(context, topic, msg, timeout, connection_pool): 57 | """Sends a message on a topic and wait for a response.""" 58 | rv = multicall(context, topic, msg, timeout, connection_pool) 59 | # NOTE(vish): return the last result from the multicall 60 | rv = list(rv) 61 | if not rv: 62 | return 63 | return rv[-1] 64 | 65 | 66 | def cast(context, topic, msg, connection_pool): 67 | """Sends a message on a topic without waiting for a response.""" 68 | LOG.debug(_('Making asynchronous cast on %s...'), topic) 69 | pack_context(msg, context) 70 | with ConnectionContext(connection_pool) as conn: 71 | conn.topic_send(topic, msg) 72 | ``` 73 | 74 | -------------------------------------------------------------------------------- /windows/essentials_win.md: -------------------------------------------------------------------------------- 1 | # My essential software list on Windows 2 | 3 | ## Daily use 4 | 5 | ### Babun 6 | 7 | A Windows shell package 8 | 9 | https://github.com/babun/babun 10 | 11 | ### 7-Zip 12 | 13 | File archiver 14 | 15 | http://www.7-zip.org 16 | 17 | ### Chrome 18 | 19 | Internet browser 20 | 21 | http://www.google.com/chrome 22 | 23 | ### Sublime Text 24 | 25 | Text Editor 26 | 27 | http://www.sublimetext.com 28 | 29 | ### Outlook 30 | 31 | Mail client 32 | 33 | Included in Office 365 34 | 35 | ### OneDrive 36 | 37 | Microsoft cloud storage 38 | 39 | https://onedrive.live.com 40 | 41 | ### Office 365 42 | 43 | Microsoft Office suite 44 | 45 | https://www.office.com 46 | 47 | ### Putty 48 | 49 | SSH and telnet client 50 | 51 | http://www.putty.org 52 | 53 | ### MPC-HC 54 | 55 | Media player 56 | 57 | https://mpc-hc.org 58 | 59 | ### LAVFilters 60 | 61 | DirectShow media splitter and decoders 62 | 63 | https://github.com/Nevcairiel/LAVFilters 64 | 65 | ### madVR 66 | 67 | DirectShow video renderer 68 | 69 | http://www.madvr.com 70 | 71 | ### HexChat 72 | 73 | IRC client 74 | 75 | https://hexchat.github.io 76 | 77 | ### DAEMON Tools Lite 78 | 79 | Virtual device management 80 | 81 | https://www.daemon-tools.cc/products/dtLite 82 | 83 | ### CPU-Z 84 | 85 | CPU Information 86 | 87 | http://www.cpuid.com/softwares/cpu-z.html 88 | 89 | ### GPU-Z 90 | 91 | GPU Information 92 | 93 | https://www.techpowerup.com/gpuz 94 | 95 | ### AS SSD Benchmark 96 | 97 | HDD/SSD benchmark 98 | 99 | http://www.alex-is.de/PHP/fusion/downloads.php?download_id=9 100 | 101 | ### CrystalDiskInfo 102 | 103 | HDD/SSD utility 104 | 105 | http://crystalmark.info/software/CrystalDiskInfo/index-e.html 106 | 107 | ### WinMTR 108 | 109 | Network Traceroute tools 110 | 111 | http://winmtr.net 112 | 113 | ### BestTrace 114 | 115 | Network Traceroute tools 116 | 117 | https://www.ipip.net/download.html 118 | 119 | ### VirtualBox 120 | 121 | Full virtualizer for x86 122 | 123 | https://www.virtualbox.org 124 | 125 | 126 | ## Optional 127 | 128 | ### Moeditor 129 | 130 | Markdown editor 131 | 132 | https://github.com/Moeditor/Moeditor 133 | 134 | ### Firefox 135 | 136 | Internet browser 137 | 138 | https://www.mozilla.org/en-US/firefox/new 139 | 140 | ### Bandzip 141 | 142 | File archiver 143 | 144 | http://www.bandisoft.com/bandizip 145 | 146 | ### Thunderbird 147 | 148 | Mail client 149 | 150 | https://www.mozilla.org/en-US/thunderbird 151 | 152 | ### Honeyview 153 | 154 | Image viewer 155 | 156 | http://www.bandisoft.com/honeyview 157 | 158 | ### Listary 159 | 160 | Launcher 161 | 162 | http://www.listary.com 163 | 164 | ### Ditto 165 | 166 | Clipboard manager 167 | 168 | http://ditto-cp.sourceforge.net 169 | 170 | ### VideoLAN 171 | 172 | Media and streaming player 173 | 174 | http://www.videolan.org/vlc 175 | 176 | ### mpv 177 | 178 | Media player 179 | 180 | https://mpv.io 181 | 182 | ### LICEcap 183 | 184 | Capture screen to gif 185 | 186 | http://www.cockos.com/licecap 187 | 188 | ### fliqlo screen saver 189 | 190 | A clock screen saver 191 | 192 | http://fliqlo.com/ 193 | -------------------------------------------------------------------------------- /openstack/nova-network-api.md: -------------------------------------------------------------------------------- 1 | # How nova-network api works 2 | 3 | This page briefly indicates how nova-network api call rpc functions, for more detailed rpc information please refer to [[nova-network-rpc-registry]] and [[inside-openstack-rpc]]. 4 | 5 | ## Simple RPC structure of nova-network: 6 | 7 | 8 | Publisher Consumer 9 | +--------------------+ +---------+ 10 | | | | | 11 | | .... | +-----------+ | ... | 12 | | | register topic "network" | | call topic "network" to respond | | 13 | | NetworkManager | <--------------------------> | | <---------------------------------> | API | 14 | | NetworkManager | | RPC queue | | API | 15 | | NetworkManager | register "network.host" | | call "network.host" to respond | API | 16 | | | <--------------------------> | | <---------------------------------> | | 17 | | .... | +-----------+ | ... | 18 | | | | | 19 | +--------------------+ +---------+ 20 | 21 | ## How API call RPC in code 22 | 23 | ### messages with routing key "topic" in code 24 | > nova / nova / network / api.py 25 | 26 | ```python 27 | class API(base.Base): 28 | 29 | ... 30 | def get_all(self, context): 31 | return rpc.call(context, 32 | FLAGS.network_topic, # FLAGS.network_topic = "network" 33 | {'method': 'get_all_networks'}) 34 | ... 35 | 36 | def release_floating_ip(self, context, address, 37 | affect_auto_assigned=False): 38 | """Removes floating ip with address from a project. (deallocates)""" 39 | rpc.cast(context, 40 | FLAGS.network_topic, # FLAGS.network_topic = "network" 41 | {'method': 'deallocate_floating_ip', 42 | 'args': {'address': address, 43 | 'affect_auto_assigned': affect_auto_assigned}}) 44 | ... 45 | ``` 46 | 47 | 48 | ### messages with routing key "topic.host" in code 49 | > nova / nova / network / manager.py 50 | 51 | ```python 52 | class RPCAllocateFixedIP(object): 53 | 54 | def _allocate_fixed_ips(self, context, instance_id, host, networks, **kwargs): 55 | 56 | ... 57 | topic = self.db.queue_get_for(context, 58 | FLAGS.network_topic, 59 | host) 60 | args = {} 61 | args['instance_id'] = instance_id 62 | args['network_id'] = network['id'] 63 | args['address'] = address 64 | args['vpn'] = vpn 65 | 66 | green_pool.spawn_n(rpc.call, context, topic, 67 | {'method': '_rpc_allocate_fixed_ip', 68 | 'args': args}) 69 | ... 70 | ``` 71 | -------------------------------------------------------------------------------- /openstack/inside-openstack-iptables.md: -------------------------------------------------------------------------------- 1 | # Openstack iptables structure 2 | 3 | This page shows openstack iptables structure in a more visual and human-readable way. 4 | 5 | ## table filter 6 | 7 | -->INPUT 8 | -j nova-compute-INPUT # empty by default 9 | -j nova-network-INPUT # empty by default 10 | -j nova-api-INPUT 11 | 12 | -->nova-api-INPUT 13 | -d {nova-ip-address}/32 -p tcp -m tcp --dport {nova-api-port} -j ACCEPT 14 | 15 | -->FORWARD 16 | -j nova-filter-top # shared by all vm instances 17 | -j nova-compute-FORWARD # empty by default 18 | -j nova-network-FORWARD 19 | -j nova-api-FORWARD # empty by default 20 | 21 | -->nova-filter-top # shared by both FORWARD and OUTPUT chain 22 | -j nova-compute-local 23 | -j nova-network-local # empty by default 24 | -j nova-api-local # empty by default 25 | 26 | -->nova-compute-local # instance dedicated rules 27 | -d {inst-fixed-ip}/32 -j nova-compute-inst-{inst-id} 28 | -d {inst-fixed-ip}/32 -j nova-compute-inst-{inst-id} 29 | ... 30 | 31 | -->nova-compute-inst-{inst-id} # default vm instance rules 32 | -m state --state INVALID -j DROP # drop invalid packet 33 | -m state --state RELATED,ESTABLISHED -j ACCEPT # accept related and established packet 34 | -j nova-compute-provider # empty by default 35 | -s {inst-fixed-ip}/32 -p udp -m udp --sport 67 --dport 68 -j ACCEPT 36 | # insert our ipset rules here 37 | # -m set --match-set {inst-set-name} src -j ACCEPT 38 | -p icmp -j ACCEPT 39 | -p tcp -m tcp --dport 22 -j ACCEPT # 22 is ssh default port 40 | -j nova-compute-sg-fallback 41 | 42 | -->nova-compute-sg-fallback 43 | -j DROP # drop all rest unmatched packets 44 | 45 | 46 | -->nova-network-FORWARD # accept forward packet via network bridge 47 | -i br100 -j ACCEPT 48 | -o br100 -j ACCEPT 49 | 50 | -->OUTPUT 51 | -j nova-filter-top 52 | -j nova-compute-OUTPUT # empty by default 53 | -j nova-network-OUTPUT # empty by default 54 | -j nova-api-OUTPUT # empty by default 55 | 56 | 57 | ## table nat 58 | 59 | -->PREROUTING 60 | -j nova-compute-PREROUTING # empty by default 61 | -j nova-network-PREROUTING 62 | -j nova-api-PREROUTING # empty by default 63 | 64 | -->nova-network-PREROUTING 65 | -d 169.254.169.254/32 -p tcp -m tcp --dport 80 -j DNAT --to-destination {metadata-host-ip}:{meta-host-port} # metadata host dnat 66 | -d {inst-floating-ip}/32 -j DNAT --to-destination {inst-fixed-ip} 67 | -d {inst-floating-ip}/32 -j DNAT --to-destination {inst-fixed-ip} 68 | ... 69 | 70 | -->OUTPUT 71 | -j nova-compute-OUTPUT # empty by default 72 | -j nova-network-OUTPUT # empty by default 73 | -j nova-api-OUTPUT # empty by default 74 | 75 | -->POSTROUTING 76 | -j nova-compute-POSTROUTING # empty by default 77 | -j nova-network-POSTROUTING 78 | -j nova-api-POSTROUTING # empty by default 79 | -j nova-postrouting-bottom 80 | 81 | -->nova-network-POSTROUTING 82 | -s {inst-fixed-ip-range} -d {metadata-host-ip} -j ACCEPT 83 | -s {inst-fixed-ip-range} -d {dmz-ip-range} -j ACCEPT 84 | -s {inst-fixed-ip-range} -d {inst-fixed-ip-range} -m conntrack ! --ctstate DNAT -j ACCEPT 85 | 86 | -->nova-postrouting-bottom # perform snat here 87 | -j nova-compute-snat 88 | -j nova-network-snat 89 | -j nova-api-snat 90 | 91 | -->nova-compute-snat 92 | -j nova-compute-float-snat # empty by default 93 | 94 | -->nova-network-snat 95 | -j nova-network-float-snat 96 | -s {inst-fixed-ip-range} -j SNAT --to-source {routing-source-ip} 97 | 98 | -->nova-network-float-snat 99 | -s {inst-fixed-ip} -j SNAT --to-source {inst-floating-ip} 100 | -s {inst-fixed-ip} -j SNAT --to-source {inst-floating-ip} 101 | ... 102 | 103 | -->nova-api-snat 104 | -->nova-api-float-snat # empty by default 105 | -------------------------------------------------------------------------------- /misc/Yann-Andrea-Steiner-reading-notes.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | 绝望的爱 3 | ======== 4 | 5 | --------------------------------------------- 6 | ————读《扬·安德烈亚·斯泰奈》与玛格丽特·杜拉斯 7 | --------------------------------------------- 8 | 9 | :Author: stanzgy 10 | :Date: 2008-10-2 11 | 12 | 13 | :: 14 | 15 | “我变老了,我突然发现我变老了。他也看到了这一点,他说:你累了。” 16 | “太晚了,太晚了,在我这一生中,这未免来得太早,也过于匆匆。才十八岁,就已经是太迟了。在十八岁和二十五岁之间,我原来的面貌早已不知去向。我在十八岁的时候就老了。” 17 | 18 | -- 杜拉斯《情人》 上海译文 19 | 20 | 杜拉斯的文字总是这样,让我感到一种莫名的悲伤与黯淡,仿佛生活本身就是一种巨大的痛苦,爱就是绝望。无法忘记那两年前的那一天,第一次读杜拉斯,她把我的心撕开了一道口子,一直到今天都在隐隐作痛。记得当时刚刚看完《帷幕》,自己全身心崇拜、深深地沉浸在米兰·昆德拉的理智与博学中不能自拔,只把昆德拉的作品当作文字的最完美形式。无意间拿起一本同学刚买的杜拉斯《情人·乌发碧眼》翻了起来,完全没有想到文字还能以这样一种方式存在,以一种女人的方式。可能是我阅读量不够,觉得自己看过的20世纪的小说,往往过于注重文字的形式与结构、新奇或复杂抽象的构思,而忽视了文字本身的芬芳与美,比如乔伊斯、卡尔维诺、马尔克斯,甚至奥威尔、G·戈尔丁等作家的作品都是这样。而杜拉斯是一朵奇葩,给这个“男人味”太重的20世纪小说添上了她独特的香味。 21 | 22 | 《扬·安德烈亚·斯泰奈》 23 | ---------------------- 24 | 25 | :: 26 | 27 | “痛苦,就像是解决痛苦的一种方式,就像是第二次爱情。” 28 | 29 | 杜拉丝笔下的文字总和爱离不开。她笔下的爱总是十分痛苦,痛苦的十分自然,自然的让人宁静,宁静的让人绝望。 30 | 31 | :: 32 | 33 | “我开了门。 34 | …… 35 | 接着门在你和我的身后关上。一个又高又瘦的新来者的身后。 36 | 接着有了声音。柔和的令人难以置信的声音。冷淡。庄严。这是你信中的声音。我生命的声音。” 37 | 38 | -- 杜拉斯《扬·安德烈亚·斯泰奈》上海译文 39 | 40 | 《扬·安德烈亚·斯泰奈》,一个关于犹太小男孩与女孩的故事,一个关于杜拉斯与安德烈亚的故事。第一次读它,是在一个深夜。在宿舍,我一个人,拉上了窗帘。窗外一定有很多车驶过,我看不见,我听不见。手摩挲着书页,心交给了杜拉斯。总觉得那个8岁的犹太小男孩就在身边,他的眼睛是那么大,那么蓝,他没有瞳孔,那是个黑洞,我看不见底。她在哪,那个女辅导员,仿佛听见了她的脚步声,她一定安静地蹲在某个角落看着我和小男孩,她一定扎着马尾辫,杜拉斯,这就是你么。没有回答。合上书,听不见呼吸,听不见心跳,只等着你永远沉默的回答。 41 | 42 | 第一次读《扬·安德烈亚·斯泰奈》只是沉浸于那两个犹太小孩不可能的爱情故事本身中去,并没有注意扬·安德烈亚,只当是个虚构的人。可当后来看了《杜拉斯传》后,才知道知道多纳迪厄(杜拉斯是她的笔名)与安德烈亚有这么一段传奇的旷世之恋,一个大学生与一个老太婆跨越几十年的爱情,我目瞪口呆。 43 | 44 | :: 45 | 46 | “我不叫出她的名字,我想是因为我首次读到这个名字,看到这个名字看到她的名和姓,这个名字马上把我迷住了。这个笔名。这个化名。这个作者的名字。总之,我喜欢这个名字。我永远喜欢这个名字。 47 | 事情就是这样。” 48 | 49 | -- 扬·安德烈亚《情人杜拉斯》 作家出版社 50 | 51 | 绝望的爱情。我能说些什么呢。安德烈亚,你爱的是多纳迪厄么?或许,你爱的只是那些书。或许,你爱的那个杜拉斯只存在于你的脑海里。到现在我还无法理解这场爱情,这是命运么?很难想像这场爱情的结束离现在只有十年(1996年杜拉斯去世),这并不是一个传说,我无法理解,或许我的眼光过于世俗。从传记中我只能看出安德烈亚一直在真实的杜拉斯与他心里的杜拉斯之间绝望地挣扎,绝望地挣扎了三十年,一片空白的三十年,毫无作为的三十年。安德烈亚,你若爱的是杜拉斯而不是多纳迪厄,完全不必这样,一场悲剧。 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 | 这是一种令人舒畅的忧郁,我胆怯地喜欢着这种感觉,死亡的感觉。这种感觉和读昆德拉、王小波那种强烈的存在感完全相反,让我安静,让我虚无。她用死亡来让我宁静。她在各种绝望与痛苦中、在道德与自我的冲突中选择了死亡,灵魂的死亡、爱情的死亡、道德的死亡……出生于印度支那的她,16岁却要回到法国,她无法承受这种巨大的转变,西方的文化无法接受这样一个东方的白人,她无法接受这个不接受她的社会与文化,所以,她选择以她的方式死亡。(参考《玛格丽特·杜拉斯研究》户思社著 复旦大学出版社)这种死亡,不是逃避,是一种沉默的让人窒息的抗争。这种死亡感觉,让我突然想起了阿来的《尘埃落定》,虽然两部小说差异很大,但总感觉有一些相通的地方,同样躁动不安的世界,同样亲切的死亡,同样宁静的自己。杜拉斯在爱与不爱中死亡,阿来在聪明与傻中死亡,他们都慢慢沉默、慢慢凋零。日本的作家好像也都很喜欢这种有点病态的死般的宁静,特别是村上春树。我很喜欢村上春树,他很像杜拉斯,他写的是另一种死亡,是一种病态,一种神经的错结与扭曲,他和杜拉斯同样深知爱情的极度痛苦与无奈,一种让人撕心裂肺的痛,让后让这种感觉付诸文字,来揉捏着我的每一根神经。 93 | 94 | :: 95 | 96 | “学会安宁,灵魂归入天堂的方式,对上帝用‘你’相称。” 97 | 98 | -- 杜拉斯《劳儿之劫》 上海译文 99 | 100 | 这是我的杜拉斯。 101 | 102 | 关于版本 103 | -------- 104 | 105 | 我觉得读杜拉斯的中文版小说,一定要读上海译文出版社的。杜拉斯的魅力,就在于她那美丽的文字和气氛,糟糕的翻译会毁掉一切。曾经读春风文艺出版社出的《抵挡太平洋的堤坝》和《塔吉尼亚的小马》,平庸的像巴尔扎克一样,索然无味。而上海译文出版社出的杜拉斯,翻译都十分优秀,特别是王文融先生的《扬·安德烈亚·斯泰奈》和王道乾先生的《情人·乌发碧眼》。现在书店里常见的杜拉斯全集是上海译文出版社和春风文艺出版社出版的。相比之下,上海译文的版本多用短句,文字如溪流般渗进心田,回味无穷,像在与作者对白,仿佛小说中的场景就在眼前,我更加喜欢。 106 | 107 | 杜拉斯 108 | ------ 109 | 110 | 读完《杜拉斯传》才发现原来我心中的那个完美杜拉斯并不存在,那个内敛、细腻、高贵的杜拉斯,那个应该坐在阁楼上书桌边安静写作的杜拉斯,那个会写作时偷偷照照镜子的杜拉斯并不存在。不存在。我无法相信,我无法接受。真实的杜拉斯常年酗酒、性格暴躁、放荡不羁。真奇怪,为么会这样,那样的她怎么能写出这样的作品,我困惑不已。我总认为有两个杜拉斯,一个写作,一个生活,但她们到底是怎样合为一体的,我不知道答案。那些文字告诉我多纳迪厄不是杜拉斯,不可能是。我错了么,或许她就是她,多纳迪厄,杜拉斯,她是一个人,只不过我自己又一厢情愿臆造了另一个杜拉斯。我没有读懂你么?我不知道答案。是那酒精给了你无穷的灵感么?是那放荡的生活给了你独特的生活感受让你写出这些如此迷人的作品么?我不知道答案。是东方与西方文化的冲突压迫的你非要靠酒精与放荡来释放解脱么?我不知道答案。 111 | 112 | 答案已死去十多年,或许答案自己也不知道答案是什么,或许根本就不存在什么答案。 113 | 114 | 我陷入了围城。 115 | -------------------------------------------------------------------------------- /openstack/ci-migration-summary.md: -------------------------------------------------------------------------------- 1 | # CI 系统迁移小结 2 | 3 | 由于之前我们使用了四年的 Gerrit 服务器过于老旧需要下线,加上之前田田离职、Jenkins 等测试服务无人维护的缘故,我接手并借着这个机会将我们目前使用的整个 CI 系统升级迁移到了新服务器上。下面是这次迁移过程的小结。 4 | 5 | > TL;DR: 请大家尽快登录新 Gerrit,在个人设置页面设置 corp 邮箱前缀作为 Gerrit 用户名,方便我为大家的账号添加权限。 6 | > [https://review.cloud.netease.com](https://review.cloud.netease.com) 7 | 8 | 9 | ## Openstack CI 系统简介 10 | 11 | Openstack 社区的 CI 系统 workflow 如下图所示: 12 | 13 | ![CI_workflow_community](CI_workflow_community.png) 14 | 15 | 图中涉及到的 CI 服务/组件说明: 16 | 17 | * Gerrit: 代码 Review 工具 18 | * Jenkins: 测试核心服务,图中特指 Jenkins master 节点 19 | * Zuul: Openstack 社区开发的新服务,用来关联 Gerrit 和 Jenkins 服务。Zuul 通过 gerrit stream-events 监听 Gerrit 上的各种事件,并根据配置触发 Jenkins 运行对应的测试任务 20 | * CI Cloud: 所有 Jenkins 的 slave 运行在一个独立维护 Openstack 环境中,每个 slave 实际上就是一个 VM 21 | * Nodepool: 维护 Openstack 环境中 slave VM 的服务,会自动创建 slave、并在 slave 运行完一次测试后删除并重建 VM 22 | * Jenkins Job Builder: 由于 Openstack 下子项目众多,几乎不可能为每个项目手工维护这么多 Jenkins 测试任务的配置,社区开发了这个工具自动生成、注入测试任务的设置到 Jenkins 中 23 | * 其他还有一些不是很重要的 PyPI/Puppet/Ansible/Gearman 等服务 ,以及 Jenkins slave 运行完各种任务进行文档/安装包/日志等内容发布的外围服务器,这里不做详细说明 24 | 25 | 26 | ## 老 CI 系统存在的问题 27 | 28 | 我们目前运行的 CI 系统是大概三年前从无到有徒手建立,陪我们逐步成长,慢慢发展完善到目前的样子,不过也存在一些主要问题: 29 | 30 | 1. Jenkins 纯手工维护。目前 Jenkins 有接近 100 个测试任务,每个任务都有复杂的测试步骤与各种环境变量的配置。如果当前 Jenkins 服务器损坏,需要手工重建或迁移的话,工作量与难度不可想象。 31 | 2. 慢!目前每个 Gerrit 提交几乎都要等一个小时才能获得 Jenkins 运行结果,比较影响开发效率。 32 | 3. 目前的 Openstack CI Cloud 问题很大。目前我们的 Openstack CI Cloud 是用一个很老的 Openstack 版本搭建的,纯手工维护,几乎不敢做任何操作,一旦出现问题很难解决;由于Openstack 版本太老,没有安装包、配置麻烦,非常难扩展新的物理节点;并且我们没有使用 Nodepool 服务,所有 slave 不会在跑完一次测试后重建,如果某个测试对 slave vm 造成了破坏,会一直保留下来,这些破坏累积起来可能导致 slave 不能工作。 33 | 4. 各个项目非常错误的通过维护私有 PyPI 源中的软件包版本来控制测试环境的建立,这个私有 PyPI 源一旦损坏将导致整个 CI 环境瘫痪。 34 | 35 | 36 | ## 新 CI 系统如何解决这些问题 37 | 38 | 有了是用老 CI 系统的这些经验,我们在迁移升级新 CI 系统时就可以吸取经验,针对性解决掉这些问题: 39 | 40 | 1. 引入社区的 Jenkins Job Builder 工具,自动化生成管理 Jenkins 的任务配置,不管是 100 还是 1000 个 Jenkins 任务,都只需要一条命令行就可以生成管理。 41 | 2. 引入几乎是为运行 CI 测试量身定做的 Docker 服务,使用 Docker containers 代替目前老旧的 Openstack VMs 运行测试任务,能能充分利用宿主机性能,并且省掉了删除建立虚拟机的时间。以 Neutron 项目为例,实测 py27 单元测试时间从原来的 24 分钟缩短到 7 分钟、tempest full 测试从 46 分钟缩短到了 18 分钟。 42 | 3. 引入 Jenkins Docker Plugin,只需要使用 Dockerfile 生成了运行测试的 docker slave image,在 Jenkins 中简单设置以后就可以运行测试了。扩展新物理节点及其方便,只需要在新节点安装 Docker 服务,再在 Jenkins 中做简单配置就可以完成。同时 Docker container 一次性使用的天然特性直接帮我们省略掉了 Nodepool 服务去创建/删除/重建 slave vm 的大量耗时工作。 43 | 4. 花了近一个月的时间整理我们各个项目的 requirement.txt,并且搭建新的私有 PyPI server。使用符合 PEP-440 规范的版本命名方法重新整理了 requirements.txt 和新 PyPI server 中的软件包,使 requirements.txt 中的依赖可以同时兼容社区版本与我们自己的二次开发版本。 44 | 45 | 46 | ## 新 CI 系统 workflow 47 | 48 | ![CI_workflow_netease](CI_workflow_netease.png) 49 | 50 | 和社区的 CI 系统相比,主要区别是我们使用 Jenkins Docker Plugin 和 Docker 服务替换了社区使用的 Nodepool 和 Openstack Cloud,其他部分基本一样。 51 | 52 | 和我们目前的 CI 系统相比,除了与社区的区别,主要是将所有组件的版本从三年前的老版本同步到与社区一致的最新版本,并且引入了 Jenkins Job Builder 自动管理 Jenkins 任务。 53 | 54 | ## 新 Jenkins 测试任务说明 55 | 56 | * {project}-pep8: pep8检查,自动在项目目录运行 tox -epep8 57 | * {project}-python27: 单元测试,自动在项目目录运行 tox -epy27 58 | * {project}-pylint: pylint检查,部分项目有,自动在项目目录运行 tox -epylint,目前由于 docker 节点较少暂时关闭 59 | * gate-tempest-dsvm-umbrella: tempest 的 umbrella 冒烟用例 60 | * gate-tempest-dsvm-neutron: tempest 的 neutron 冒烟用例 61 | * gate-tempest-dsvm-full: tempest 的 full 冒烟用例 62 | 63 | ## 新 Gerrit 使用说明 64 | 65 | ### 身份认证 66 | 67 | 新 Gerrit 身份认证从以前的 LDAP 切换到了新的 OpenID 上,大家第一次登录后请在 Gerrit 个人设置页面设置自己的 corp 邮箱前缀为自己的 Gerrit 用户名。 68 | 69 | > NOTE: 由于 Gerrit 的用户账号要在用户第一次登录后才会创建,请大家尽快登录新 Gerrit,方便我为大家的账号添加权限。 70 | > [https://review.cloud.netease.com](https://review.cloud.netease.com) 71 | 72 | ### 仓库管理 73 | 74 | 1. Openstack 社区已有的项目,完全镜像社区的命名方法镜像到我们的 Gerrit 上,如 openstack/nova 、 openstack-dev/devstack 75 | 2. 我们的自研项目,以及一些 patch 过的 openstack 相关项目,放入 openstack-netease 命名空间,如 openstack-netease/umbrella 、 openstack-netease/libvirt 76 | 3. 非 Openstack 的项目放入各自的命名空间,如 docker/* 77 | 78 | ### 分支管理 79 | 80 | 保持之前的管理方法,Openstack 社区的分支完全保留,我们自己的开发分支,根据使用的社区 codename 加 netease/ 前缀,如 netease/havana。 81 | 82 | ### 权限管理 83 | 84 | 之前的 Gerrit 权限比较混乱,目前新的 Gerrit 和 Openstack 社区一致,统一改为每个项目 {project} 会有一个 {project}-core 用户组为其 Owner ,具有 Code-Review +2 和 Workflow +1 的权限。 85 | 86 | > 需要注意新的 Gerrit 代码合入方式改为和社区一致,core 用户 Workflow +1 后,Jenkins 会运行一些测试,通过后由 Zuul 将代码合入仓库。(而不是现在的直接 Submit 按钮手工合入) 87 | 88 | 云主机相关的项目 core 为王盼,云网络相关的项目 core 为徐城利。具体项目可单独调整。 89 | 90 | ### 新的 Zuul Status 页面 91 | 92 | 从社区搬过来了新的 Zuul status page ,使用 graphite 绘制,代替之前非常朴素的 plain text 页面。 93 | 94 | 预览: 95 | 96 | ![zuul_status](zuul_status.png) 97 | 98 | 99 | ## 遗留问题 100 | 101 | 由于一些之前田田留下的打包任务,在新的打包方案使用前仍需保留,所以老的 Jenkins 和 Gerrit 仍然继续运行一段时间,田田打包相关的任务请提交到老 CI 系统触发。在新的打包系统投入使用后,老的 Gerrit 和 Jenkins 将下线。 102 | 103 | ## Links 104 | 105 | * Gerrit: 106 | [https://review.cloud.netease.com](https://review.cloud.netease.com) 107 | * Gerrit 性能监控插件: [https://review.cloud.netease.com/monitoring](https://review.cloud.netease.com/monitoring) 108 | * Jenkins: [https://jenkins.cloud.netease.com](https://jenkins.cloud.netease.com) 109 | * Zuul status: [https://zuul.cloud.netease.com/](https://zuul.cloud.netease.com/) 110 | * PyPI Server: [https://pypi.cloud.netease.com](https://pypi.cloud.netease.com) -------------------------------------------------------------------------------- /network/how-tcpkill-works.md: -------------------------------------------------------------------------------- 1 | # tcpkill工作原理分析 2 | 3 | 日常工作生活中大家在维护自己的服务器、VPS有时会碰到这样的情况:服务器上突然出现了许多来自未知ip的网络连接与流量,我们需要第一时间切断这些可能有害的网络连接。除了iptables/ipset, blackhole routing这些常规手段,我们还可以借助一些更轻量级的小工具来临时处理这些情况,如tcpkill。 4 | 5 | 6 | ## tcpkill使用简介 7 | 8 | tcpkill是一个网络分析工具集dsniff中的一个小工具。在Debian/Ubuntu上可以直接通过dsniff包安装: 9 | 10 | # aptitude install dsniff 11 | 12 | tcpkill使用的语法和tcpdump几乎一致: 13 | 14 | tcpkill [-i interface] [-1...9] expression 15 | 16 | 其中第一个参数 `-i` 指定网卡设备。 17 | 18 | 第二个参数指定“kill”的强制等级,越高越强,默认为3,我们在后面了解tcpkill的工作原理后会知道这个参数的具体作用。 19 | 20 | 第三个参数则是匹配需要kill的tcp连接通配表达式,语法与tcpdump使用的pcap-filter完全一样。 21 | 22 | 如果我们需要使用tcpkill临时禁止服务器与主机10.0.0.1的tcp连接,可以在服务器上输入命令: 23 | 24 | # tcpkill host 10.0.0.1 25 | 26 | tcpkill会一直阻止主机10.0.0.1与服务器的网络连接,直到你结束这个tcpkill进程位置。 27 | 28 | 29 | ## tcpkill原理分析 30 | 31 | 在使用tcpkill时,会发现一件奇怪的事情,运行tcpkill命令后并不会马上中断匹配的tcp连接,只有当该连接有新的tcp包发送接收时,tcpkill才会“kill”这个tcp连接。这个奇怪的现象燃起了我们的好奇心,于是探索一下tcpkill到底是如何工作的。 32 | 33 | 下面以Linux下的nc命令为例。 34 | 35 | 我们在两个主机hostA与hostB间通过nc命令建立一个tcp连接: 36 | 37 | hostA在本地tcp 5555端口监听 38 | 39 | hostA$ nc -l -p 5555 40 | 41 | hostB通过本地6666端口连接hostA的5555端口 42 | 43 | hostB$ nc hostA 5555 -p 6666 44 | 45 | 此时在hostA上已经可以观察到一条与hostB的ESTABLISHED连接 46 | 47 | hostA$ netstat -anp|grep 5555 48 | tcp 0 0 hostA:5555 hostB:6666 ESTABLISHED 19638/nc 49 | 50 | 在hostA上通过tcpdump也可以观察到3次握手已经完成 51 | 52 | hostA# tcpdump -i eth1 port 5555 53 | IP hostB.6666 > hostA.5555: Flags [S], seq 750827752, ... 54 | IP hostA.5555 > hostB.6666: Flags [S.], seq 1191909671, ack 750827753, ... 55 | IP hostB.6666 > hostA.5555: Flags [.], ack 1, win 115, ... 56 | 57 | 如果此时运行tcpkill命令尝试“kill”这个tcp连接 58 | 59 | hostA# tcpkill -1 -i eth1 port 5555 60 | tcpkill: listening on eth1 [port 5555] 61 | 62 | 会发现hostA与hostB上的nc命令并没有受到任何影响而退出,hostA上观察到该tcp连接还是ESTABLISHED状态,tcpdump与tcpkill也没有任何新的输出。 63 | 64 | hostA$ netstat -anp|grep 5555 65 | tcp 0 0 hostA:5555 hostB:6666 ESTABLISHED 19638/nc 66 | 67 | 运行tcpkill命令后,建立好的tcp连接并没有受到任何影响。 68 | 69 | 如果我们此时在hostB的nc上输入任意字符发送,则会发现这时tcp连接中断,nc发送失败退出。 70 | 71 | hostB$ nc hostA 5555 -p 6666 72 | a 73 | (exit) 74 | hostB$ 75 | 76 | hostA上的nc监听进程也因为连接中断而退出 77 | 78 | hostA$ nc -l -p 5555 79 | (exit) 80 | hostA$ 81 | 82 | netstat已经观察不到这个tcp连接,而tcpdump此时则捕获了一个新tcp rst包: 83 | 84 | hostA# tcpdump -i eth1 port 5555 85 | IP hostB.6666 > hostA.5555: Flags [S], seq 750827752, ... 86 | IP hostA.5555 > hostB.6666: Flags [S.], seq 1191909671, ack 750827753, ... 87 | IP hostB.6666 > hostA.5555: Flags [.], ack 1, win 115, ... 88 | IP hostA.5555 > hostB.6666: Flags [R], seq 1191909672, ... 89 | 90 | 此时tcpkill的输出 91 | 92 | hostA# tcpkill -1 -i eth1 port 5555 93 | tcpkill: listening on eth1 [port 5555] 94 | hostB:6666 > hostA:5555: R 1191909672:1191909672(0) win 0 95 | hostA:5555 > hostB:6666: R 750827755:750827755(0) win 0 96 | 97 | 相信看到这里,已经可以明白tcpkill的工作原理,实际上就是通过双向fake tcp rst包重置目标连接双方的网络连接,和某墙的原理一样。 98 | 99 | 而之所以tcpkill不会马上中断目标tcp连接,是因为伪造tcp rst包时,需要填入正确的sequence number,这需要通过拦截双方的tcp通信才能实时得到。所以运行tcpkill后,只有目标连接有新tcp包发送/接受才会导致tcp连接中断。 100 | 101 | 最后分析一下tcpkill第二个参数的具体作用。manpage里的说明比较模糊,只能看出和receive window有关: 102 | 103 | > -1...9 Specify the degree of brute force to use in killing a connection. Fast connections may require a higher number in order to land a RST in the moving receive window. Default is 3. 104 | 105 | 直接看源代码(只有100多行) 106 | 107 | ... 108 | 109 | int 110 | main(int argc, char *argv[]) 111 | { 112 | ... 113 | 114 | /* 通过libpcap抓取所有符合条件的包,回调函数为tcp_kill_cb */ 115 | pcap_loop(pd, -1, tcp_kill_cb, (u_char *)&sock); 116 | 117 | ... 118 | } 119 | 120 | static void 121 | tcp_kill_cb(u_char *user, const struct pcap_pkthdr *pcap, const u_char *pkt) 122 | { 123 | ... 124 | 125 | /* 只处理tcp包 */ 126 | ip = (struct libnet_ip_hdr *)pkt; 127 | if (ip->ip_p != IPPROTO_TCP) 128 | return; 129 | 130 | /* 不处理tcp syn/fin/rst包 */ 131 | tcp = (struct libnet_tcp_hdr *)(pkt + (ip->ip_hl << 2)); 132 | if (tcp->th_flags & (TH_SYN|TH_FIN|TH_RST)) 133 | return; 134 | 135 | /* 伪造ip包 */ 136 | libnet_build_ip(TCP_H, 0, 0, 0, 64, IPPROTO_TCP, 137 | ip->ip_dst.s_addr, ip->ip_src.s_addr, 138 | NULL, 0, buf); 139 | 140 | /* 伪造tcp rst包 */ 141 | libnet_build_tcp(ntohs(tcp->th_dport), ntohs(tcp->th_sport), 142 | 0, 0, TH_RST, 0, 0, NULL, 0, buf + IP_H); 143 | 144 | /* fake tcp rst包的sequence number即为抓到的包的ack number */ 145 | seq = ntohl(tcp->th_ack); 146 | 147 | ... 148 | 149 | /* 这里Opt_severity即为tcpkill的第二个参数 */ 150 | win = ntohs(tcp->th_win); 151 | for (i = 0; i < Opt_severity; i++) { 152 | ip->ip_id = libnet_get_prand(PRu16); 153 | seq += (i * win); 154 | tcp->th_seq = htonl(seq); 155 | 156 | libnet_do_checksum(buf, IPPROTO_TCP, TCP_H); 157 | 158 | /* 发送伪造的tcp rst包 */ 159 | if (libnet_write_ip(*sock, buf, sizeof(buf)) < 0) 160 | warn("write_ip"); 161 | 162 | fprintf(stderr, "%s R %lu:%lu(0) win 0\n", ctext, seq, seq); 163 | } 164 | } 165 | 166 | 从上面可以看出,tcpkill的第二个参数,实际上就是沿tcp连接窗口滑动而发送的tcp rst包个数。将这个参数设置较大主要是应对高速tcp连接的情况。 167 | 168 | 参数的大小从中断tcp连接的原理上没有区别,只是发送rst包数量的差异,通常情况下使用默认值3已经完全没有问题了。所以使用tcpkill时请不要像网络上某些中文教程中一样不适当的使用参数 `-9` 。 169 | -------------------------------------------------------------------------------- /macos/essentials_osx.md: -------------------------------------------------------------------------------- 1 | # My essential software list on MacOS 2 | 3 | ## Daily use 4 | 5 | ### iTerm2 6 | 7 | Terminal replacement. 8 | 9 | https://iterm2.com 10 | 11 | ### Homebrew 12 | 13 | The missing package manager for OS X 14 | 15 | http://brew.sh 16 | 17 | ### Alfred Powerpack 18 | 19 | Launcher 20 | 21 | https://www.alfredapp.com 22 | 23 | ### The Unarchiver 24 | 25 | Archive extractor 26 | 27 | Install from MAS 28 | 29 | ### Chrome 30 | 31 | Internet browser 32 | 33 | http://www.google.com/chrome 34 | 35 | ### Outlook 36 | 37 | Mail client 38 | 39 | Install from MAS 40 | 41 | ### OneDrive 42 | 43 | Microsoft cloud storage 44 | 45 | Install from MAS 46 | 47 | ### Office 365 48 | 49 | Microsoft Office suite 50 | 51 | https://www.office.com 52 | 53 | ### Surge 54 | 55 | Fxxk GFW 56 | 57 | http://nssurge.com 58 | 59 | ### Karabiner-Elements 60 | 61 | Keyboard customizer 62 | 63 | https://github.com/tekezo/Karabiner-Elements 64 | 65 | ### Sublime Text 66 | 67 | Text Editor 68 | 69 | http://www.sublimetext.com 70 | 71 | ### Moeditor 72 | 73 | Markdown editor 74 | 75 | https://github.com/Moeditor/Moeditor 76 | 77 | ### iStat Menus 78 | 79 | System monitor 80 | 81 | https://bjango.com/mac/istatmenus 82 | 83 | ### IINA 84 | 85 | https://lhc70000.github.io/iina 86 | 87 | ### Tunnelblick 88 | 89 | OpenVPN client 90 | 91 | https://tunnelblick.net 92 | 93 | ### Little Snitch 94 | 95 | Network firewall 96 | 97 | https://www.obdev.at/products/littlesnitch/index.html 98 | 99 | ### Bartender 100 | 101 | Menubar organizer 102 | 103 | http://www.macbartender.com 104 | 105 | ### Spectacle 106 | 107 | Window organizer 108 | 109 | https://github.com/eczarny/spectacle 110 | 111 | ### KeepingYouAwake 112 | 113 | Activated to prevent going into sleep 114 | 115 | https://github.com/newmarcel/KeepingYouAwake 116 | 117 | ### NoSleep 118 | 119 | Prevent sleep when close the lid 120 | 121 | https://github.com/integralpro/nosleep 122 | 123 | ### PopClip 124 | 125 | Text select enhancement 126 | 127 | Install from MAS 128 | 129 | ### Padbury clock screen saver 130 | 131 | A clock screen saver 132 | 133 | http://padbury.me/clock 134 | 135 | ### Display Menu 136 | 137 | Display management 138 | 139 | http://displaymenu.milchimgemuesefach.de 140 | 141 | ### Clean My Mac 142 | 143 | System cleanup 144 | 145 | http://macpaw.com/cleanmymac 146 | 147 | ### Microsoft Remote Desktop 148 | 149 | Microsoft remote desktop 150 | 151 | Install from MAS 152 | 153 | ### IntelliJ IDEA 154 | 155 | Java/Scala IDE 156 | 157 | https://www.jetbrains.com/idea 158 | 159 | ### Sequel Pro 160 | 161 | MySQL database management 162 | 163 | http://www.sequelpro.com 164 | 165 | ### PSequel 166 | 167 | PostgreSQL database management 168 | 169 | http://www.psequel.com 170 | 171 | ### f.lux 172 | 173 | Screen color auto adjustment 174 | 175 | https://justgetflux.com 176 | 177 | ### Noizio 178 | 179 | White noise generator 180 | 181 | http://noiz.io 182 | 183 | ### BestTrace 184 | 185 | Network Traceroute tools 186 | 187 | Install from MAS 188 | 189 | ### MacPass 190 | 191 | Personal password management 192 | 193 | https://github.com/mstarke/MacPass 194 | 195 | 196 | ## Optional 197 | 198 | ### VideoLAN 199 | 200 | Media and stream player 201 | 202 | http://www.videolan.org/vlc 203 | 204 | ### ShadowsocksX-NG 205 | 206 | Fxxk GFW 207 | 208 | https://github.com/shadowsocks/ShadowsocksX-NG 209 | 210 | ### Firefox 211 | 212 | Internet browser 213 | 214 | https://www.mozilla.org/en-US/firefox/new 215 | 216 | ### Thunderbird 217 | 218 | Mail client 219 | 220 | https://www.mozilla.org/en-US/thunderbird 221 | 222 | ### Telegram 223 | 224 | Instant messenger 225 | 226 | https://macos.telegram.org 227 | 228 | ### Twitter 229 | 230 | Twitter 231 | 232 | Install from MAS 233 | 234 | ### MacDown 235 | 236 | Markdown editor 237 | 238 | https://github.com/MacDownApp/macdown 239 | 240 | ### AppCleaner 241 | 242 | App uninstall tool 243 | 244 | http://www.freemacsoft.net/appcleaner 245 | 246 | ### mpv 247 | 248 | Media player 249 | 250 | https://mpv.io 251 | 252 | ### AirServer 253 | 254 | Screen mirroring software receiver for Mac/PC 255 | 256 | http://www.airserver.com 257 | 258 | ### CatchMouse 259 | 260 | Quick move mouse among screens 261 | 262 | http://ftnew.com/catchmouse.html 263 | 264 | ### Gas Mask 265 | 266 | Hosts file manager 267 | 268 | https://github.com/2ndalpha/gasmask 269 | 270 | ### LICEcap 271 | 272 | Capture screen to gif 273 | 274 | http://www.cockos.com/licecap 275 | 276 | ### Skitch 277 | 278 | Picture annotation 279 | 280 | https://evernote.com/skitch 281 | 282 | ### CheatSheet 283 | 284 | Show active shortcuts of current application 285 | 286 | http://www.mediaatelier.com/CheatSheet 287 | 288 | ### DynamicLyrics 289 | 290 | Display iTunes lyrics in the top Menu Bar 291 | 292 | https://martianz.cn/dynamiclyrics 293 | 294 | ### fliqlo screen saver 295 | 296 | A clock screen saver 297 | 298 | http://fliqlo.com 299 | 300 | ### coconut Battery 301 | 302 | Show Mac and iOS devices battery health 303 | 304 | http://www.coconut-flavour.com/coconutbattery 305 | 306 | ### QBlocker 307 | 308 | Block unintended CMD + Q 309 | 310 | https://github.com/steve228uk/QBlocker 311 | 312 | 313 | ## Obsolete 314 | 315 | ### XtraFinder 316 | 317 | Finder enhancement, not working under 10.12 318 | 319 | https://www.trankynam.com/xtrafinder 320 | 321 | ### Karabiner 322 | 323 | Keyboard customizer, replaced by Karabiner-Elements 324 | 325 | https://pqrs.org/osx/karabiner/index.html.en 326 | 327 | ### Seil 328 | 329 | Remap Caps Lock key, replaced by Karabiner-Elements 330 | 331 | https://pqrs.org/osx/karabiner/seil.html.en 332 | -------------------------------------------------------------------------------- /programming/python_3_coroutines.md: -------------------------------------------------------------------------------- 1 | ## 小议Python3的原生协程机制 2 | 3 | 在最近发布的 Python 3.5 版本中,官方正式引入了 `async`/`await`关键字、在 4 | `asyncio` [1] 标准库中实现了IO多路复用、原生协程(coroutine)与 5 | 事件循环(event loop),让人耳目一新,本文也尝试对 Python 3.5 新增加的原生协程 6 | 机制与`asyncio`标准库相关的内容做一个小结。 7 | 8 | IO多路复用与协程的引入,可以极大的提高高负载下程序的IO性能表现。几年前,M$在 9 | C# 中便已经通过引入 `async`/`await` 关键字实现了一套异步IO机制,成为业界模范, 10 | Python现在引入相同的编程范式也算博众家之长。 11 | 12 | 事实上,在官方实现原生协程机制前,在目前比较流行的 Python 2.X 版本中,由于 13 | GIL [2] 的存在,Python 程序的多线程/多进程性能非常不理想,使得协程成为 Python 14 | 并发编程的最佳模型,大量的 Python 项目都开始通过使用第三方库实现的协程编写程序 15 | (如 eventlet / gevent )、特别是网络编程相关的 Python 项目。不过限于 Python 2 16 | 语言实现的局限,协程的实现比较原始,众多第三方库的实现并不统一,并且通常都需要 17 | 使用一些特殊的编程技巧(monkey patch / green 标准库等手段)才能实现非阻塞IO等特 18 | 性来真正提高性能。 19 | 20 | 相比之下,直接官方提供标准库原生支持"async io"与协程的 Python 3.5 编写程序无疑 21 | 更加方便。 22 | 23 | 24 | ### async/await 25 | 26 | 官方在 PEP-492 [3] 中定义了 `async`/`await` 关键字来使用协程。 27 | 28 | 声明一个协程非常简单,通过 `async` 实现: 29 | 30 | async def foo(): 31 | pass 32 | 33 | 在普通的函数声明前加上`async`关键字,这个函数就变成了一个协程。 34 | 35 | 需要注意的是,在 `async def` 定义的协程内,不能含有 `yield` 或 `yield from` 36 | 表达式,否则会报 SyntaxError 异常。 37 | 38 | `await`表示等待另一个协程执行完成返回,必须在协程内才能使用。 39 | 40 | 下面是一个官方文档中提供的协程简单示例,非常直观的表示了 `async`/`await`、协程 41 | 与事件循环的执行过程: 42 | 43 | import asyncio 44 | 45 | async def compute(x, y): 46 | print("Compute %s + %s ..." % (x, y)) 47 | await asyncio.sleep(1.0) 48 | return x + y 49 | 50 | async def print_sum(x, y): 51 | result = await compute(x, y) 52 | print("%s + %s = %s" % (x, y, result)) 53 | 54 | loop = asyncio.get_event_loop() 55 | loop.run_until_complete(print_sum(1, 2)) 56 | loop.close() 57 | 58 | ![mitmproxy](../asset/py3_coro_1.png) 59 | 60 | 从图中可以看到,`asyncio` 标准库自带的事件循环负责所有协程的调度。在首先执行 61 | print_sum 协程时,内部使用`await`等待另外一个协程compute的返回结果,于是本地 62 | 协程被挂起去执行协程compute。而协程compute执行过程中调用了 63 | `await asyncio.sleep(1.0)`,于是本地协程挂起1秒、将程序控制权交回事件循环,1秒 64 | 后再恢复协程compute的执行直到整个stack执行结束。 65 | 66 | 而在大量协程并发执行的过程中,除了在协程中主动使用 `await`,当本地协程发生 67 | IO等待、调用 `asyncio.sleep()`等方法时,程序的控制权也会在不同的协程间切换,从 68 | 而在 GIL 的限制下实现最大程度的并发执行,不会由于等待IO等原因导致程序阻塞,达到 69 | 较高的性能表现。 70 | 71 | 值得一提的是,`asyncio` 和类似 `libev` 一样的众多第三方类库实现的事件循环一样, 72 | 在不同平台上会使用不同的轮询机制,比如在Linux平台上使用 poll/epoll、BSD平台上 73 | 使用 kqueue、NT内核上会直接使用Proactor模式的完成端口。 74 | 75 | 76 | ### yield from 77 | 78 | 如果有一直关注 Python 3 原生协程实现的同学,应该会知道其实它是靠生成器 79 | (Generator)实现的。`yield from` 则是在 PEP-380 [4] 中新增加的生成器定义 80 | 关键字,与原生协程的实现密不可分。 81 | 82 | 原理上 `yield from` 基本等价于 `await`,只是 `await` 针对协程的实现做了更多具体 83 | 的处理与约定。 84 | 85 | `yield from` 表达式的语义定义有很长的一串,要彻底搞明白它的实现,需要先学习在 86 | PEP-342 [5] 中描述的增强型生成器(Enhanced Generators),但这里我们可以简单的把 87 | 它看作一个生成器语法糖: 88 | 89 | for v in g: 90 | yield v 91 | 92 | 加上在生成器内 93 | 94 | return value 95 | 96 | 等价于 97 | 98 | raise StopIteration(value) 99 | 100 | 这样实现以后,像这样的表达式 101 | 102 | y = f(x) 103 | 104 | 我们就可以用 `yield from` 语法将 f(x) 改造成协程实现了 105 | 106 | y = yield from g(x) 107 | 108 | g 是 f 的生成器,只需要保证两者最终返回值一致,我们并不关心生成器 g 的中间 109 | 状态。 110 | 111 | 如果要详细了解这里的实现,建议还是阅读 PEP-380 与 PEP-342 原文,里面有专门的 112 | 章节专门描述这个问题。 113 | 114 | 115 | ### context manager 116 | 117 | PEP-492 中让人眼前一亮的一点是定义了协程的上下文管理器(context manager),新增 118 | 了 `async with` 语法,让我们可以将一个上下文作为协程处理,在进入(enter)和退出 119 | (exit)一个 BLOCK 时做协程调度操作: 120 | 121 | async with EXPR as VAR: 122 | BLOCK 123 | 124 | 与普通的 context manager 相比,async 版本的上下文管理器在内部方法前加了字母 "a" 125 | 126 | class AsyncContextManager: 127 | async def __aenter__(self): 128 | await log('entering context') 129 | 130 | async def __aexit__(self, exc_type, exc, tb): 131 | await log('exiting context') 132 | 133 | 一种典型的应用就是进入临界区 134 | 135 | class Lock: 136 | async def __aenter__(self): 137 | await self.lock.lock() 138 | 139 | async def __aexit__(self, exc_type, exc, tb): 140 | await self.lock.release() 141 | 142 | 143 | async with Lock(): 144 | ... 145 | 146 | 类似的语法还有 `async for` 147 | 148 | async for TARGET in ITER: 149 | BLOCK 150 | 151 | 这里不再赘述。 152 | 153 | 154 | ### promise/future 155 | 156 | `promise` 是一种最近在 nodejs 流行起来另外一种异步编程范式,在不同的地方可能也 157 | 被称作 `future` / `deferred`,但一般都指的是同一种类似的东西。`promise` 并没有 158 | 一个非常官方的标准,我了解的比较知名的`promise`标准规范有 `Promises A+` [6]。 159 | Python 从 Python 3.4 开始也提供了 `asyncio.Future` 实现类似的功能。 160 | 161 | `promise` 的核心思想是为一个异步操作定义操作成功和失败的不通情况下的的回调 162 | 函数。在 nodejs 这类缺少官方 `await` 语法支持的语言中,能有效减轻 163 | `callback hell` 问题,让代码更简洁,减少 raw callback 写法导致的缩进太多的 164 | 问题,并能方便的实现链式操作。 165 | 166 | 而在 Python 中,语言语法本身提供的功能已经足够丰富,没有 `callback hell` 这类 167 | 问题,`Future` 则可以更专注的让我们可以将各种异步操作以一种顺序的、更接近人类 168 | 逻辑思维与自然语言方式描述出来: 169 | 170 | import asyncio 171 | 172 | @asyncio.coroutine 173 | def slow_operation(future): 174 | yield from asyncio.sleep(1) 175 | future.set_result('Future is done!') 176 | 177 | loop = asyncio.get_event_loop() 178 | future = asyncio.Future() 179 | asyncio.ensure_future(slow_operation(future)) 180 | loop.run_until_complete(future) 181 | print(future.result()) 182 | loop.close() 183 | 184 | 185 | ### 小结 186 | 187 | 在现在流行的编程语言中,异步、协程、事件循环被越来越多的关注与使用,Python 中的 188 | asyncio.coroutine、Ruby 中的 Fiber、Node 中的 Promise、甚至像 Scala 这样的语言 189 | 中也有了 Future,更不用说 Golang 这种在这条路上一头走到底的编程语言。 190 | `await`/`Future` 这样的异步编程方式被越来越多的普及与使用, 191 | 以后可能会像 if、for 一样成为编程语言不可缺少的一部分,而协程这种轻量级线程在 192 | 某些情境下可能也会越来越多的代替目前的多线程/多进程成为主流的并发编程方式。 193 | 194 | 美中不足的是,秉承 Guido 一向以来只挖坑不填坑的习惯,Python 3.5 实现新的 195 | `async`/`await` 关键字后,并没有给出具体的现有第三方类库如何向新的 native 协程 196 | 实现迁移的方案,更不用说一些流行的 Python 2 第三方类库目前连 Python 3 都不 197 | 支持。距离我们真正能方便流畅地使用体验它可能还需要一段时间。 198 | 199 | 200 | ### References 201 | 202 | [1]: https://docs.python.org/3/library/asyncio.html 203 | [2]: https://wiki.python.org/moin/GlobalInterpreterLock 204 | [3]: https://www.python.org/dev/peps/pep-0492 205 | [4]: https://www.python.org/dev/peps/pep-0380 206 | [5]: https://www.python.org/dev/peps/pep-0342 207 | [6]: https://github.com/promises-aplus/promises-spec 208 | [7]: https://lwn.net/Articles/643786 209 | -------------------------------------------------------------------------------- /openstack/setup-neutron-dev-env-from-src.md: -------------------------------------------------------------------------------- 1 | # 从源码安装配置Neutron开发环境 2 | 3 | ## 安装配置havana版本nova/glance/keystone服务 4 | 5 | 安装配置过程略 6 | 7 | ### 修改nova.conf中与neutron相关的配置 8 | 9 | network_api_class=nova.network.neutronv2.api.API 10 | neutron_url=http://%neutron_host%:9696 11 | neutron_admin_auth_url=http://%neutron_admin_host%:5000/v2.0 12 | neutron_admin_username=%neutron_admin_user% 13 | neutron_admin_password=%neutron_admin_pw% 14 | neutron_admin_tenant_name=service=%neutron_admin_tenant% 15 | neutron_region_name=neutron=%neutron_region% 16 | neutron_auth_strategy=keystone 17 | neutron_ovs_bridge=br-int 18 | 19 | libvirt_vif_driver=nova.virt.libvirt.vif.LibvirtOpenVswitchDriver 20 | 21 | ## 编译安装Open vSwitch 22 | 23 | ### 确认内核版本 24 | 25 | 确认kernel版本 =3.8, kernel在3.8版本以后才支持vxlan 26 | 27 | ### 安装依赖 28 | 29 | 请根据源码目录下的INSTALL文件确认build-essential, libc, libssl, clang, 30 | iproute2, linux-headers, autoconf, automake, python, 31 | perl等包正确安装并且版本符合要求. 32 | 33 | ### 获得源代码 34 | 35 | % git clone git://git.openvswitch.org/openvswitch 36 | 37 | ### 通过制作deb安装包安装openvswitch(推荐) 38 | 39 | #### 将openvswitch源码编译成deb安装包 40 | 41 | % cd openvswitch 42 | 43 | % dpkg-buildpackage -b -rfakeroot 44 | 45 | 打包完成后, 生成的openvswitch deb安装包默认存放在上一级目录下 46 | 47 | #### 安装openvswitch安装包 48 | 49 | % dpkg -i openvswitch-common_1.12.90-1_amd64.deb 50 | 51 | % dpkg -i openvswitch-controller_1.12.90-1_amd64.deb 52 | 53 | % dpkg -i openvswitch-datapath-dkms_1.12.90-1_all.deb 54 | 55 | % dpkg -i openvswitch-pki_1.12.90-1_all.deb 56 | 57 | % dpkg -i openvswitch-switch_1.12.90-1_amd64.deb 58 | 59 | % dpkg -i openvswitch-test_1.12.90-1_all.deb 60 | 61 | #### 启动openvswitch服务 62 | 63 | % sudo service openvswitch-switch restart 64 | 65 | ### 通过编译源代码手动安装openvswitch 66 | 67 | #### configure 68 | 69 | % ./boot.sh 70 | 71 | % ./configure --prefix=/usr --with-linux=/lib/modules/`uname -r`/build 72 | 73 | #### make & install 74 | 75 | % make 76 | 77 | % make install 78 | 79 | % make modules_install 80 | 81 | % modprobe openvswitch 82 | 83 | #### 启动openvswitch服务 84 | 85 | % mkdir -p /usr/local/etc/openvswitch 86 | 87 | % ovsdb-tool create /usr/local/etc/openvswitch/conf.db vswitchd/vswitch.ovsschema 88 | 89 | % ovsdb-server --remote=punix:/usr/local/var/run/openvswitch/db.sock 90 | --remote=db:Open_vSwitch,Open_vSwitch,manager_options 91 | --private-key=db:Open_vSwitch,SSL,private_key 92 | --certificate=db:Open_vSwitch,SSL,certificate 93 | --bootstrap-ca-cert=db:Open_vSwitch,SSL,ca_cert 94 | --pidfile --detach 95 | 96 | % ovs-vsctl --no-wait init 97 | 98 | % ovs-vswitchd --pidfile --detach 99 | 100 | ### 确认openvswitch正常运行 101 | 102 | * ovsdb-server, ovs-vswitchd两个进程正常运行 103 | * sudo ovs-vsctl show命令无错误输出 104 | 105 | ## 编译安装Neutron服务 106 | 107 | ### 获得源代码 108 | 109 | % git clone https://github.com/openstack/neutron.git 110 | 111 | ### 安装neutron 112 | 113 | 请根据需要选择neutron的版本, 这里使用的是master分支的最新代码 114 | 115 | % cd neutron 116 | 117 | % git checkout master 118 | 119 | % python setup.py build 120 | 121 | % sudo python setup.py install 122 | 123 | ### 修改neutron.conf中的相关选项 124 | 125 | 常规选项请自行修改, 这里列举一些关键选项的配置 126 | 127 | #### ML2 configs 128 | 129 | [ml2] 130 | # ml2 plugin configs 131 | type_drivers = vxlan,gre,vlan,local 132 | tenant_network_types = vxlan,gre,vlan,local 133 | 134 | [ml2_type_vxlan] 135 | vni_ranges=1:1000 136 | vxlan_group=239.1.1.1 137 | 138 | #### DHCP agent 139 | 140 | [DEFAULT] 141 | # dhcp-agent configs 142 | ovs_use_veth = True 143 | enable_isolated_metadata = True 144 | use_namespaces = True 145 | 146 | #### L3 agent 147 | 148 | [DEFAULT] 149 | # l3-agent configs 150 | # OVS based plugins (OVS, Ryu, NEC) that supports L3 agent 151 | interface_driver = neutron.agent.linux.interface.OVSInterfaceDriver 152 | 153 | #### Open vSwitch agent 154 | 155 | [agent] 156 | # openvswitch plugin configs 157 | tunnel_types = vxlan,gre 158 | 159 | [ovs] 160 | # openvswitch plugin configs 161 | local_ip = 10.120.120.31 162 | #bridge_mappings = physnet1:br-eth1.301 163 | tenant_network_type = vxlan 164 | # vlan type 165 | #network_vlan_ranges = 166 | enable_tunneling = True 167 | tunnel_type = vxlan 168 | tunnel_id_ranges = 1:1000 169 | 170 | ### 启动neutron服务 171 | 172 | #### 主节点 173 | 174 | * neutron-server 175 | * neutron-dhcp-agent 176 | * neutron-openvswitch-agent 177 | 178 | #### 从节点 179 | 180 | * neutron-openvswitch-agent 181 | 182 | ### 建立L2网络 183 | 184 | #### 建立vxlan隧道网络 185 | 186 | % neutron net-create --provider:network_type vxlan 187 | --provider:segmentation_id 10 188 | --tenant-id TENANT_ID --shared 189 | net1 190 | 191 | #### 建立gre隧道网络 192 | 193 | % neutron net-create --provider:network_type gre 194 | --provider:segmentation_id 20 195 | --tenant-id TENANT_ID --shared 196 | net2 197 | ### 建立L3网络 198 | 199 | % neutron subnet-create net1 10.0.10.0/24 200 | 201 | % neutron subnet-create net2 10.0.20.0/24 202 | 203 | ### 建立虚拟机, 使用neutron的网络 204 | 205 | % nova boot --image debian-6.0.4-amd64-standard 206 | --flavor 2 207 | --nic net-id=NETWORK_ID 208 | test_neutron 209 | 210 | 如果可以正常创建虚拟机, 说明nova和neutron已经可以正常工作了 211 | 212 | ## 常见问题 213 | 214 | ### neutron服务提示br-int不存在, 无法启动 215 | 216 | 手工创建br-int 217 | 218 | % ovs-vsctl add-br br-int 219 | 220 | ### neutron配置文件不生效 221 | 222 | neutron默认只读取/etc/neutron/neutron.conf中的配置项, 对于/etc/neutron/plugins 223 | 和/etc/neutron目录下的其他配置文件, 需要在启动neutron服务时手工指定配置文件才可 224 | 生效. 225 | 226 | 推荐将所有配置项都写入到/etc/neutron/neutron.conf中, 这样启动服务时所有配置 227 | 不许要指定配置文件就可生效, 比较省事. 228 | 229 | ### openvswitch网络跨节点无法互通 230 | 231 | 曾经发现openvswitch隧道不通的问题, 原因是内核的ip_gre模块和openvswitch模块冲突. 232 | 将ip_gre模块卸载后, 手工加载gre模块和openvswitch模块可以解决这个问题. 233 | 234 | ### 使用neutron网络后, 如何连接虚拟机 235 | 236 | 方法一: 配置L3 agent, 给虚拟机绑定floating ip, 通过floating ip访问虚拟机 237 | 238 | 方法二: 通过dhcp的net namespace访问 239 | 240 | 在dhcp agent运行的宿主机上查找虚拟机所在的net namespace, 并切换到相应的 241 | net namespace内 242 | 243 | % ip netns list 244 | 245 | % ip netns exec NETNS_NAME bash 246 | 247 | 在net namespace内, 可以直接通过虚拟机分配到的IP访问虚拟机. 248 | 249 | -------------------------------------------------------------------------------- /openstack/setup-devstack.md: -------------------------------------------------------------------------------- 1 | ## Devstack 开发环境搭建指南 2 | 3 | 搭建 Devstack 开发环境一直是一件挺头疼的事情,特别是对于新人来说, 4 | 容易遇到各种各样的问题而失败。 5 | 最近借着整理 Jenkins 的机会把我们的 Havana 版本 Devstack 重新整理了一遍, 6 | 修复了一些问题,使其可以比较简单快速的搭建起来一个 Openstack 开发环境。 7 | 8 | 下面详细说明。 9 | 10 | ### 单节点 Devstack 安装 11 | 12 | #### 准备一台 Ubuntu 14.04 虚拟机 13 | 14 | 我们目前建议直接在云计算线上环境 (yun.163.com) 创建你的虚拟机开发环境。 15 | 16 | > 请注意一定要使用 Ubuntu 14.04 的镜像创建虚拟机 17 | 18 | 创建好虚拟机后,需要你做一些最基本的系统设置、初始化工作, 19 | 比如创建你自己的账号、安装 vim git sudo 等基础软件等工作,这里不做赘述。 20 | 21 | > 如果你经常创建虚拟机,可以使用 ansible/puppet 22 | > 等工具来初始化虚拟机,非常方便 23 | 24 | #### 设置 Python 环境 25 | 26 | 确认安装必要的依赖包 27 | 28 | $ aptitude install python-dev libffi-dev libssl-dev 29 | 30 | 创建/修改你的 pip.conf 配置文件,将 index-url 修改为我们的私有 PyPI 源 31 | 32 | $ cat ~/.pip/pip.conf 33 | [global] 34 | index-url = https://pypi.cloud.netease.com/jenkins/dev/+simple 35 | timeout=30 36 | 37 | 安装/更新 pip tox virtualenv 等常用软件 38 | 39 | $ pip install -U 'pip<8.1.2' tox virtualenv ndg-httpsclient pyopenssl pyasn1 40 | 41 | > 请注意,目前发现 pip 版本 >=8.1.2 时某些情况下会有兼容性问题。 42 | 43 | #### 获取 Devstack 源码 44 | 45 | Gitlab 46 | 47 | $ git clone ssh://git@g.hz.netease.com:22222/openstack-dev/devstack -b netease/havana 48 | 49 | 或 Gerrit 50 | 51 | $ git clone ssh://${your_gerrit_acct}@review.cloud.netease.com:2222/openstack/devstack -b netease/havana 52 | 53 | > 请注意要使用 netease/havana 分支 54 | 55 | #### 创建你的 localrc 配置文件 56 | 57 | 这里可以直接使用我刚刚更新的 localrc 文件,见 devstack/samples/localrc 58 | 59 | # General settings 60 | export OS_NO_CACHE=True 61 | 62 | DEST=/opt/stack/new 63 | DATA_DIR=/opt/stack/data 64 | 65 | VERBOSE=True 66 | USE_SCREEN=True 67 | SYSLOG=False 68 | LOG_COLOR=True 69 | SCREEN_LOGDIR=/opt/stack/new/screen-logs 70 | LOGFILE=/opt/stack/new/devstacklog.txt 71 | DATABASE_QUERY_LOGGING=True 72 | 73 | IMAGE_URLS=https://review.cloud.netease.com/static/cirros-0.3.0-x86_64-disk.img 74 | TEMPEST_HTTP_IMAGE=https://review.cloud.netease.com/static/cirros-0.3.0-x86_64-disk.img 75 | 76 | ACTIVE_TIMEOUT=90 77 | BOOT_TIMEOUT=90 78 | ASSOCIATE_TIMEOUT=60 79 | TERMINATE_TIMEOUT=60 80 | ROOTSLEEP=0 81 | 82 | MYSQL_PASSWORD=secretmysql 83 | DATABASE_PASSWORD=secretdatabase 84 | RABBIT_PASSWORD=secretrabbit 85 | ADMIN_PASSWORD=secretadmin 86 | SERVICE_PASSWORD=secretservice 87 | SERVICE_TOKEN=111222333444 88 | SWIFT_HASH=1234123412341234 89 | 90 | ENABLED_SERVICES=dstat,g-api,g-reg,key,mysql,n-api,n-cond,n-cpu,n-crt,n-sch,q-agt,q-dhcp,q-meta,q-svc,q-vpn,quantum,rabbit 91 | SKIP_EXERCISES=boot_from_volume,bundle,client-env,euca 92 | SERVICE_HOST=127.0.0.1 93 | # Don't reset the requirements.txt files after g-r updates 94 | UNDO_REQUIREMENTS=False 95 | TRACK_DEPENDS=False 96 | 97 | 98 | # Git settings 99 | GIT_BASE=ssh://git@g.hz.netease.com:22222 100 | ERROR_ON_CLONE=False 101 | LIBS_FROM_GIT= 102 | REQUIREMENTS_BRANCH=netease/havana 103 | PBR_BRANCH=netease 104 | NOVA_BRANCH=netease/havana 105 | GLANCE_BRANCH=netease/havana 106 | KEYSTONE_BRANCH=netease/havana 107 | NEUTRON_BRANCH=netease/havana 108 | CINDER_BRANCH=netease/havana 109 | NOVACLIENT_BRANCH=netease/havana 110 | GLANCECLIENT_BRANCH=netease/havana 111 | KEYSTONECLIENT_BRANCH=netease/havana 112 | NEUTRONCLIENT_BRANCH=netease/havana 113 | CINDERCLIENT_BRANCH=netease/havana 114 | OPENSTACKCLIENT_BRANCH=netease/havana 115 | TEMPEST_BRANCH=netease/havana 116 | UMBRELLA_BRANCH=netease/havana 117 | SENTRY_BRANCH=netease/havana 118 | 119 | 120 | # Neutron settings 121 | NETWORK_GATEWAY=10.1.0.1 122 | ENABLE_TENANT_TUNNELS=True 123 | TENANT_TUNNEL_RANGE=50:100 124 | Q_PLUGIN=ml2 125 | Q_USE_DEBUG_COMMAND=False 126 | Q_USE_SECGROUP=False 127 | Q_SERVICE_PLUGIN_CLASSES='router,vpnaas' 128 | Q_SERVICE_PROVIDER=VPN:sslvpn:neutron.services.vpn.service_drivers.sslvpn.SSLVPNDriver:default 129 | Q_ML2_TENANT_NETWORK_TYPE=vxlan,gre 130 | Q_ML2_PLUGIN_VXLAN_TYPE_OPTIONS=(vni_ranges=400:500) 131 | Q_ML2_PLUGIN_MECHANISM_DRIVERS=openvswitch,svlan,l2population 132 | Q_ML2_PLUGIN_TYPE_DRIVERS=vxlan,gre,svlan,flat 133 | 134 | 135 | # Nova settings 136 | LIBVIRT_TYPE=qemu 137 | VIRT_DRIVER=libvirt 138 | LIBVIRT_INJECT_PARTITION=-2 139 | LIBVIRT_FIREWALL_DRIVER=nova.virt.firewall.NoopFirewallDriver 140 | NOVA_VIF_DRIVER=nova.virt.libvirt.vif.LibvirtGenericVIFDriver 141 | LINUXNET_VIF_DRIVER=nova.network.linux_net.LinuxOVSInterfaceDriver 142 | # set this until all testing platforms have libvirt >= 1.2.11 143 | # see bug #1501558 144 | EBTABLES_RACE_FIX=True 145 | FORCE_CONFIG_DRIVE=True 146 | 147 | FIXED_RANGE=10.1.0.0/20 148 | FLOATING_RANGE=172.24.5.0/24 149 | PUBLIC_NETWORK_GATEWAY=172.24.5.1 150 | FIXED_NETWORK_SIZE=4096 151 | 152 | CINDER_PERIODIC_INTERVAL=10 153 | CINDER_SECURE_DELETE=False 154 | CINDER_VOLUME_CLEAR=none 155 | CEILOMETER_BACKEND=mysql 156 | SWIFT_REPLICAS=1 157 | VOLUME_BACKING_FILE_SIZE=24G 158 | 159 | 这份配置将从 g.hz.netease.com 拉取各个项目的代码并安装一个 nova/neutron 的最小 160 | openstack 开发环境,其中 161 | 162 | ENABLED_SERVICES=dstat,g-api,g-reg,key,mysql,n-api,n-cond,n-cpu,n-crt,n-sch,q-agt,q-dhcp,q-meta,q-svc,q-vpn,quantum,rabbit 163 | 164 | 变量控制 devstack 开启的服务,如果需要开启 umbrella/sentry 等服务, 165 | 将这些服务加入该变量即可。 166 | 167 | 所有的项目源码安装在 `/opt/stack/new` 目录下, 168 | 所有服务的 data 文件夹在 `/opt/stack/data` 目录下, 169 | 服务日志在 `/opt/stack/new/screen-logs` 目录下。 170 | 171 | 请注意其中的这段配置设置了各个服务的密码,请将其更新为你需要的内容 172 | 173 | MYSQL_PASSWORD=secretmysql 174 | DATABASE_PASSWORD=secretdatabase 175 | RABBIT_PASSWORD=secretrabbit 176 | ADMIN_PASSWORD=secretadmin 177 | SERVICE_PASSWORD=secretservice 178 | SERVICE_TOKEN=111222333444 179 | SWIFT_HASH=1234123412341234 180 | 181 | 如果你需要修改各个项目的 git 仓库地址与使用的分支,则需要修改下面这段内容 182 | 183 | GIT_BASE=ssh://git@g.hz.netease.com:22222 184 | ... 185 | 186 | 这份配置默认将 screen 输出自动着色,在日志文件中会出现许多乱码一样的颜色格式字符, 187 | 如果你不想这样,设置变量 188 | 189 | LOG_COLOR=False 190 | 191 | 其他许多配置项请自行研究修改,有不明白的地方可以提出来和大家交流。 192 | 193 | #### 运行 Devstack 194 | 195 | 准备好 localrc 文件后,将其放到 devstack 根目录下,就可以直接运行 devstack 了。 196 | 197 | $ cd devstack 198 | $ ./stack.sh 199 | 200 | 视机器性能与网络条件,一般过 20 分钟到 1 个小时不等,不报任何错误执行完的话, 201 | 一个单节点的 devstack 开发环境就建立好了。 202 | 203 | 如果运行 `stack.sh` 脚本的过程中发生了任何错误,请根据错误提示解决后, 204 | 清理好环境再重新运行 `stack.sh` 脚本。 205 | 206 | ### 多节点 Devstack 环境搭建 207 | 208 | 多节点 Devstack 环境,说白了就是几个单节点 Devstack 环境放在一起公用相同的 209 | database/rabbitmq/keystone 等公共服务实现,通过修改配置即可实现,这里不做详细 210 | 说明。 211 | 212 | ### Devstack 环境的使用 213 | 214 | Devstack 环境建立成功后,默认会创建两个 tenant: admin/demo 215 | 可以通过 Devstack 提供的 openrc 文件获取对应 tenant 的身份信息。 216 | 217 | $ cd devstack 218 | $ source openrc admin admin 219 | (run commands as admin) 220 | ... 221 | $ source openrc demo demo 222 | (run commands as demo) 223 | 224 | ### 其他 225 | 226 | 如果使用 Devstack 过程中发现 Devstack 源码有任何问题/Bug,欢迎直接通过 Gerrit 227 | 提交 patchset 修复。 228 | -------------------------------------------------------------------------------- /network/bgp-on-vpn.md: -------------------------------------------------------------------------------- 1 | ## BGP路由在VPN网络上失效问题探究 2 | 3 | ### 背景 4 | 5 | 由于科学上网的需求,我在多个云服务提供商处购买了数台云主机,并使用VPN服务使这些云主机运行在 6 | 加密的虚拟子网下,网络拓扑如下图示意: 7 | 8 | +------------+ +------------+ +------------+ 9 | | My_PC | | Cloud_A | | Cloud_B | 10 | | | | | | | +----------------+ 11 | | +-------+ | +-------+ | +-------+ | | 12 | | | VPN_A |----------+ VPN_A +---+---------+ VPN_A +--+--+ Target Network | 13 | | +-------+ | +-------+ | | +-------+ | | e.g.: 8.8.8.8 | 14 | | | | | | | | | | | 15 | +----------- + +----------- + | +------------+ | +----------------+ 16 | | | 17 | | +------------+ | 18 | | | Cloud_C | | 19 | | | | | 20 | | | +-------+ | 21 | +---------+ VPN_A +--+ 22 | | +-------+ 23 | | | 24 | +------------+ 25 | 26 | 27 | 图中有3台分别不同云服务提供商的云主机 `Cloud_A`/`Cloud_B`/`Cloud_C`,通过一个VPN网络 `VPN_A` 28 | 连接在一起。我的个人电脑 `My_PC` 也通过接入 `VPN_A` 实现了和这些云主机的连接。 29 | 这些云主机中,`Cloud_A` 扮演中间节点的角色,`Cloud_B` 和 `Cloud_C` 扮演网络出口的角色。 30 | 当我的个人电脑 `My_PC` 需要通过 `VPN_A` 访问IP地址 8.8.8.8 时,可以通过 31 | `My_PC` --> `Cloud_A` --> `Cloud_B` --> 8.8.8.8 路径到达,也可通过 32 | `My_PC` --> `Cloud_A` --> `Cloud_C` --> 8.8.8.8 路径到达。 33 | 34 | 为了方便控制VPN转发路径的选择,我通过在 `My_PC` 和 `Cloud_B` 、 `Cloud_C` 之间跑BGP协议, 35 | 由 `Cloud_B` `Cloud_C` 广播相同的目标网段,并尝试通过设置不同的 BGP Med 权重属性控制不同线路的 36 | 优先级。 37 | 38 | ### 问题描述 39 | 40 | 上面的网络设计在传统的物理网络上运行应是没有问题的,但是在我的VPN网络运行则遇到奇怪的路由失效 41 | 问题。下面是实际的问题描述。 42 | 43 | 首先看我个人电脑上的BGP状态: 44 | 45 | # show ip bgp 8.8.8.8 46 | BGP routing table entry for 8.0.0.0/7 47 | Paths: (6 available, best #5, table default) 48 | Advertised to non peer-group peers: 49 | 10.2.10.61 10.2.10.71 10.2.20.61 10.2.20.71 10.2.30.61 10.2.30.71 50 | ... 51 | 64900 52 | 10.2.30.71 from 10.2.30.71 (10.2.1.71) 53 | Origin incomplete, metric 510, valid, external, best (MED) 54 | Last update: Mon Mar 9 03:43:17 2020 55 | 64900 56 | 10.2.30.61 from 10.2.30.61 (10.2.1.61) 57 | Origin incomplete, metric 520, valid, external 58 | Last update: Mon Mar 9 02:15:34 2020 59 | 60 | 根据收到的BGP路由,我的个人电脑访问 8.8.8.8 应通过 10.2.30.71 这个Med最低的网关IP中转。 61 | 操作系统内核的路由表也和BGP路由一致: 62 | 63 | ❯ ip r get 8.8.8.8 64 | 8.8.8.8 via 10.2.30.71 dev tun24 src 10.2.30.13 uid 1000 65 | cache 66 | 67 | 但是当我 mtr 8.8.8.8 时,奇怪的问题发生了,实际的网络路径并没有通过 10.2.30.71 中转, 68 | 而是走了BGP MED优先级低的 10.2.30.61 ! 69 | 70 | ❯ mtr -rn 8.8.8.8 71 | HOST: xxx Loss% Snt Last Avg Best Wrst StDev 72 | 1.|-- 10.2.30.91 0.0% 10 0.8 0.9 0.7 1.1 0.1 73 | 2.|-- 10.2.30.61 30.0% 10 59.6 62.7 37.5 76.6 14.4 74 | ... 75 | 17.|-- 108.170.236.206 0.0% 10 88.1 103.8 87.0 120.9 14.0 76 | 18.|-- 108.170.238.103 0.0% 10 108.1 102.1 79.4 128.8 14.3 77 | 19.|-- 8.8.8.8 0.0% 10 93.1 101.7 84.7 120.0 12.6 78 | 79 | 这个奇怪的现象一下让我楞住了,在各个软件配置、路由表信息都正确的情况下,实际的网络并没有按 80 | 路由表生效的规则走。 81 | 82 | ### 问题分析 83 | 84 | 对于这个问题,冷静下来分析一下,网关IP到底是如何工作的? 85 | 当我们的电脑需要给不在本地网段的IP发送网络报文时,需要将这个报文发送给网关,然后由网关进行转发。 86 | 而发送给网关的方式,则是将网络报文的二层目的MAC地址填为网关的MAC地址。 87 | 88 | 对于我们的问题,抓包查一下到底是什么情况: 89 | 90 | ❯ sudo tcpdump -XX -eni tun24 icmp 91 | tcpdump: verbose output suppressed, use -v or -vv for full protocol decode 92 | listening on tun24, link-type RAW (Raw IP), capture size 262144 bytes 93 | 11:20:46.175642 IP 10.2.30.13 > 8.8.8.8: ICMP echo request, id 4, seq 1, length 64 94 | 0x0000: 4500 0054 f327 4000 4001 0f63 0a02 1e0d E..T.'@.@..c.... 95 | 0x0010: 0808 0808 0800 e58f 0004 0001 de8b 655e ..............e^ 96 | 0x0020: 0000 0000 0dae 0200 0000 0000 1011 1213 ................ 97 | 0x0030: 1415 1617 1819 1a1b 1c1d 1e1f 2021 2223 .............!"# 98 | 0x0040: 2425 2627 2829 2a2b 2c2d 2e2f 3031 3233 $%&'()*+,-./0123 99 | 0x0050: 3435 3637 4567 100 | 11:20:46.274575 IP 8.8.8.8 > 10.2.30.13: ICMP echo reply, id 4, seq 1, length 64 101 | 0x0000: 4500 0054 0000 0000 2901 598b 0808 0808 E..T....).Y..... 102 | 0x0010: 0a02 1e0d 0000 ed8f 0004 0001 de8b 655e ..............e^ 103 | 0x0020: 0000 0000 0dae 0200 0000 0000 1011 1213 ................ 104 | 0x0030: 1415 1617 1819 1a1b 1c1d 1e1f 2021 2223 .............!"# 105 | 0x0040: 2425 2627 2829 2a2b 2c2d 2e2f 3031 3233 $%&'()*+,-./0123 106 | 0x0050: 3435 3637 4567 107 | 108 | 可以看到直接就是4500开头的IP报文,没有二层的MAC信息。 109 | 这时我才意识到,我的VPN使用的是 Tun 设备的纯三层网络,报文不包含二层MAC信息,所以依赖二层 110 | MAC地址的网关IP也失效了。 111 | 而这样纯三层的网络,转发路径纯粹是由VPN软件本身控制的。 112 | 113 | 查看一下VPN软件3层转发路径选择的相关代码: 114 | 115 | subnet_t *lookup_subnet_ipv4(const ipv4_t *address) { 116 | subnet_t *p, *r = NULL; 117 | avl_node_t *n; 118 | int i; 119 | 120 | // Check if this address is cached 121 | ... 122 | 123 | // Search all subnets for a matching one 124 | 125 | for(n = subnet_tree->head; n; n = n->next) { 126 | p = n->data; 127 | 128 | if(!p || p->type != SUBNET_IPV4) { 129 | continue; 130 | } 131 | 132 | if(!maskcmp(address, &p->net.ipv4.address, p->net.ipv4.prefixlength)) { 133 | r = p; 134 | 135 | if(p->owner->status.reachable) { 136 | break; 137 | } 138 | } 139 | } 140 | 141 | // Cache the result 142 | 143 | cache_ipv4_slot = !cache_ipv4_slot; 144 | memcpy(&cache_ipv4_address[cache_ipv4_slot], address, sizeof(*address)); 145 | cache_ipv4_subnet[cache_ipv4_slot] = r; 146 | cache_ipv4_valid[cache_ipv4_slot] = true; 147 | 148 | return r; 149 | } 150 | 151 | 可以看到VPN软件本身的路径选择算法非常简单,就是选择第一个匹配到的节点。 152 | 对于我们这种subnet相同的两个节点,VPN软件是按节点名字的字母顺序排列的: 153 | 154 | Subnet list: 155 | ... 156 | 10.2.30.61/32#10 owner aws_jpn1 157 | 10.2.30.71/32#10 owner gcp_hkc1 158 | ... 159 | 0.0.0.0/0#10 owner aws_jpn1 160 | 0.0.0.0/0#10 owner gcp_hkc1 161 | ... 162 | 163 | 可以看到 10.2.30.61 节点名字的字母序比 10.2.30.71 靠前,对于同样广播的 0.0.0.0/0 这条路由, 164 | VPN软件自然就选择了 10.2.30.61 作为转发路径,而没有按我们预想的路由表工作。 165 | 至此,这个问题终于得到了解答。 166 | 167 | ### 问题解决 168 | 169 | 知道了问题的原因后,解决就比较容易了,有两个思路: 170 | 171 | 1. 将VPN改为支持二层信息的 Tap 设备模式,这样我们通过BGP发布实现的网关优先级就可以工作了 172 | 2. 网络转发路径的权重配置,通过配置VPN不同节点的优先级实现,而不是通过BGP Med配置 173 | -------------------------------------------------------------------------------- /debian_package/new_way_to_build_python_debian_package.md: -------------------------------------------------------------------------------- 1 | # Python服务Debian打包新思路 2 | 3 | Debian 打包一直是比较冷僻的技术,大部分同学都不会接触到它。 4 | 但是我们 Debian 服务器上安装的各种软件服务,都是通过各种打包工具制作出来的安装包部署到服务器上的。 5 | 6 | Debian 打包虽然比较烦琐复杂,但是它提供了比较健全的一整套软件部署、安装、升级、维护的流程, 7 | 并有一系列与之配套的自动化工具,可以避免人工操作可能出现各种遗漏、错误,特别是在大规模部署时基本不可能人工操作。 8 | 9 | 我们云计算使用的 Openstack 基础服务,也是通过自己从头制作安装包、上传到 Debian 仓库、并最终通过 puppet 等自动化工具实现服务的部署、更新。 10 | 11 | 之前我们一直采用 Debian 官方的流程对 Openstack 的 Python 服务打包,但是在几年的实践中发现了各种无法解决的问题, 12 | 不得不自己另外实施一套全新的打包方案。 13 | 14 | 本文主要介绍 Debian 新打包方案的起因、原理、流程。 15 | 16 | ## 起因 17 | 18 | 我们以前使用了很长时间的 Debian 社区官方的 Openstack 服务打包方案,中间还尝试过一段时间的 virtualenv 打包方案,各自都有较大的问题,详见下面。 19 | 20 | ### 社区打包方案 21 | 22 | 原来我们从 Debian 社区 Openstack 项目打包仓库 fork 出来的自己做一些修改和 backport 然后打包的方案, 23 | 所有服务及其依赖的 Python 模块都通过 Debian 的 `deb` 格式安装包安装,这样存在一个主要问题:Debian 官方仓库中的 Python 模块版本更新太慢了。 24 | 25 | 比如常用的 Python 数据库第三方模块 SQLAlchemy ,在 Python 的官方 PyPI 中已经更新到了 1.1.4 版本,但在 Debian Wheezy 的仓库中仅有 0.7.8 版本,差了4个大版本。 26 | 27 | 我们在使用 Openstack 服务中发现的一些数据库相关问题,本来是简单的升级 SQLAlchemy 版本就能搞定的,由于这个问题变得很难解决。 28 | 29 | 这个问题直接导致我们很难升级一些有问题的 Python 模块依赖版本;有些需要的模块甚至根本没有 Debian 安装包,引进新模块、功能比较困难;升级 Openstack 服务的大版本基本不可能,后续也基本不可能从 Debian Wheezy 升级到 Jessie 了,影响非常大。 30 | 31 | ### virtualenv 打包方案 32 | 33 | 在发现社区官方的打包方案的严重问题后,我们后面也尝试了一段时间通过 Python virtualenv 虚拟环境打包的方式, 34 | 即在一个 virtualenv 虚拟环境中通过 Python pip 工具安装相关 Python 依赖并将整个安装了服务与依赖的 virtualenv 环境打包成 Debian 安装包。 35 | 36 | 这个方案在后续使用中也发现很多问题,最大的问题还是本质上 virtualenv 并不能真正隔离系统的 Python 环境和自身虚拟环境的 Python 环境,最终导致服务各种诡异错误。 37 | 38 | 我们这里可以看一个例子 39 | 40 | $ virtualenv test 41 | $ source test/bin/activate 42 | >>> import sys 43 | >>> sys.path 44 | ['', 45 | '/home/stanzgy/workspace/test/lib/python2.7', 46 | '/home/stanzgy/workspace/test/lib/python2.7/plat-linux2', 47 | '/home/stanzgy/workspace/test/lib/python2.7/lib-tk', 48 | '/home/stanzgy/workspace/test/lib/python2.7/lib-old', 49 | '/home/stanzgy/workspace/test/lib/python2.7/lib-dynload', 50 | '/usr/lib/python2.7', 51 | '/usr/lib/python2.7/plat-linux2', 52 | '/usr/lib/python2.7/lib-tk', 53 | '/home/stanzgy/workspace/test/local/lib/python2.7/site-packages', 54 | '/home/stanzgy/workspace/test/lib/python2.7/site-packages'] 55 | >>> import json 56 | >>> json.__file__ 57 | '/usr/lib/python2.7/json/__init__.pyc' 58 | >>> import _json 59 | >>> _json.__file__ 60 | '/home/stanzgy/workspace/test/lib/python2.7/lib-dynload/_json.so' 61 | 62 | 在这个例子中,我们创建了一个虚拟环境 `test` ,并尝试在虚拟环境 import 63 | Python 自带的 `json` 模块,结果发现引用的模块地址事实上是操作系统而不是虚拟环境的。 64 | 65 | 从 `sys.path` 的结果可以看到,虚拟环境中的 Python import 模块时会尝试先从虚拟环境中的 Python PATH 搜索,然后会尝试从系统的 Python PATH 搜索。如果 import 的模块二次引用其他的 Python 模块实现,则可能导致系统的 Python 模块和虚拟环境中的 Python 模块交叉使用的情况。 66 | 67 | 在上面的例子中,可以看到 `json` 模块和实现其部分功能的 `_json` 模块分别属于系统和虚拟环境。如果系统和虚拟环境中的 Python 版本、模块版本不一致,则很容易导致服务出现问题,并且最重导致 Python 进程本身崩溃,并且很难调试、查找原因。 68 | 69 | virtualenv 打包方案从原理上并不可靠。 70 | 71 | ## 新 Debian 打包 72 | 73 | ### 需求 74 | 75 | 我们 Openstack 服务 Debian 打包在现有基础上的需求主要有三点: 76 | 1. 能自由指定、更新 Python 依赖模块版本 77 | 2. 不同 Openstack 服务之间的 Python 环境互相隔离 78 | 3. Openstack 服务的 Python 环境和系统的 Python 环境隔离 79 | 80 | 社区的方案三点都不满足,virtualenv 方案只满足第一、二点。 81 | 82 | ### 原理 83 | 84 | 新打包的流程比较复杂,但原理用一句话就能描述清楚: 85 | 每次打包独立编译 Python ,编译时通过设置 `RPATH` 变量实现隔离效果。 86 | 87 | `RPATH` 是 Python 编译时设置的变量,效果是硬编码指定并限制程序运行时动态链接库的的搜索路径,类似 `LD_LIBRARY_PATH`。关于它的详细信息和讨论可以参考 [Wikipedia][1] 和 [Debian Wiki][2] 88 | 89 | [1]: https://en.wikipedia.org/wiki/Rpath 90 | [2]: https://wiki.debian.org/RpathIssue 91 | 92 | 我们每个服务都使用不同的`RPATH`变量编译 Python 后,相当于每个服务都安装在一个独立的 Python 隔离环境里,使用各自独立的运行时动态链接库搜索路径。这样每个服务既可以随意更新修改自己的 Python 依赖模块版本、也避免了之前 virtualenv 方案存在的严重的系统环境隔离问题,解决了上面的三点需求。 93 | 94 | 下面是一个采用了新打包方案的 Openstack 服务的 Python 环境。 95 | 96 | >>> import sys 97 | >>> sys.path 98 | ['', 99 | '/srv/stack/nova/lib/python27.zip', 100 | '/srv/stack/nova/lib/python2.7', 101 | '/srv/stack/nova/lib/python2.7/plat-linux2', 102 | '/srv/stack/nova/lib/python2.7/lib-tk', 103 | '/srv/stack/nova/lib/python2.7/lib-old', 104 | '/srv/stack/nova/lib/python2.7/lib-dynload', 105 | '/srv/stack/nova/lib/python2.7/site-packages'] 106 | >>> import json 107 | >>> json.__file__ 108 | '/srv/stack/nova/lib/python2.7/json/__init__.py' 109 | >>> import _json 110 | >>> _json.__file__ 111 | '/srv/stack/nova/lib/python2.7/lib-dynload/_json.so' 112 | 113 | 可以看到它有独立的 Python sys.path 路径、并且不存在和系统的 Python 交叉调用的问题。 114 | 115 | ### 流程 116 | 117 | 新 Debian 打包方案的流程,可简单描述为: 118 | 1. 指定 `RPATH`,编译、安装 Python 119 | 2. 使用新编译的 Python pip 安装依赖 120 | 3. 安装 Python 服务到新编译好的 Python 独立环境 121 | 4. 将上面创建的整个 Python 独立环境打包 122 | 5. 处理其他配置文件、启动脚本等 123 | 124 | 下面为每个步骤的详细说明 125 | 126 | #### 编译 Python 127 | 128 | 所有项目编译 Python 使用同一个 `build-python.sh` 脚本 129 | 130 | #!/bin/bash 131 | 132 | ... 133 | 134 | export PROJECT_PREFIX=${PROJECT_PREFIX:-/srv/stack} 135 | export PROJECT_BASE=$PROJECT_PREFIX/$PROJECT 136 | export PYTHON_FILE=${PYTHON_FILE:-Python-2.7.12.tar.xz} 137 | 138 | # Get python tarball 139 | CUR_DIR=$PWD 140 | TEMP_DIR=$(mktemp -d /tmp/pybuild.XXXX) 141 | cd $TEMP_DIR 142 | wget $PYTHON_URL/$PYTHON_FILE 143 | mkdir -p py27 && tar Jxf $PYTHON_FILE -C py27 --strip-components=1 144 | cd py27 145 | 146 | # Compile python 147 | DPKG_CPPFLAGS=$(dpkg-buildflags --get CFLAGS) 148 | DPKG_CFLAGS=$(dpkg-buildflags --get CPPFLAGS) 149 | DPKG_LDFLAGS=$(dpkg-buildflags --get LDFLAGS) 150 | 151 | CFLAGS="$DPKG_CFLAGS $DPKG_CPPFLAGS" \ 152 | LDFLAGS="$DPKG_LDFLAGS,-rpath=$PROJECT_BASE/lib" \ 153 | ./configure \ 154 | --prefix=$PROJECT_BASE \ 155 | --enable-shared \ 156 | --enable-unicode=ucs2 \ 157 | --with-ensurepip=install \ 158 | --enable-ipv6 \ 159 | --with-dbmliborder=bdb:gdbm \ 160 | --with-fpectl \ 161 | --with-system-expat \ 162 | --with-system-ffi 163 | 164 | make 165 | make install 166 | cd $CUR_DIR 167 | rm -rf $TEMP_CIR 168 | 169 | 这个脚本会自动下载 Python 2.7.12 ,并指定 `/srv/stack/${PROJECT}` 为每个 Openstack 项目的 $BASE 目录,`/srv/stack/${PROJECT}/lib` 为其 `RPATH` 目录,设定一些编译选项后编译安装 Python 到 $BASE 目录。 170 | 171 | #### 安装 Python 依赖 172 | 173 | 目前新打包的 Python 依赖在上面编译 Python 后通过 pip 安装。 174 | 175 | export REQUIREMENT_FILE=debian/deb-requirements.txt 176 | 177 | # install pip requirements inside the bootstrap system 178 | $(PROJECT_PREFIX)/$(PROJECT)/bin/pip install \ 179 | -U -r $(CURDIR)/$(REQUIREMENT_FILE) 180 | 181 | 每个 Openstack 项目可以在其项目根目录下 `$REQUIREMENT_FILE` 中指定符合 python-pip 格式的 Python 依赖,这个文件通常是通过 `pip freeze` 生成的。 182 | 183 | #### 安装服务 184 | 185 | 在 Python 依赖安装成功后,安装项目本身到独立 Python 环境中。 186 | 187 | # install the project inside the bootstrap system 188 | $(PROJECT_PREFIX)/$(PROJECT)/bin/python setup.py clean \ 189 | build --executable "$(PROJECT_PREFIX)/$(PROJECT)/bin/python" install 190 | 191 | 需要注意的是,在安装项目时需要指定我们自己编译的 Python 路径 ` --executable "$(PROJECT_PREFIX)/$(PROJECT)/bin/python"` 。 192 | 193 | #### 其他 194 | 195 | 完成上面的步骤后,剩下只需要走正常的 Debian 打包流程,处理分包、配置文件等即可。 196 | 197 | ## 结语 198 | 199 | 我们使用的这个新 Python 服务 Debian 打包方案,既能享受到 Debian 包管理系统的各种便利和自动化,又能自由使用 Python PyPI 中的各种最新模块,兼顾了两者的优点又避开了各自的缺点。它已经在我们的测试环境稳定运行了几个月,趋于稳定,希望它后续能在我们服务安装部署中更好的服务我们。 200 | 201 | 关于本文有任何疑问欢迎与我交流。 -------------------------------------------------------------------------------- /network/openssl-self-signed-certs-cheatsheet.md: -------------------------------------------------------------------------------- 1 | ## Create self-signed certificates with OpenSSL 2 | 3 | ### Root CA 4 | 5 | #### Create Root CA key (enable password with '-des3' option) 6 | 7 | $ openssl genrsa -des3 -out root.key 4096 8 | 9 | 10 | #### Create Root CA 11 | 12 | $ openssl req -new -x509 -sha256 -days 3650 -key root.key -reqexts v3_req -extensions v3_ca -out root.crt 13 | 14 | 15 | ### Intermediate CA 16 | 17 | #### Create Intermediate CA key 18 | 19 | $ openssl genrsa -out ca.key 4096 20 | 21 | 22 | #### Create Intermediate CA request 23 | 24 | $ openssl req -new -key ca.key -reqexts v3_req -extensions v3_ca -out ca.csr 25 | 26 | 27 | #### Sign Intermediate CA request 28 | 29 | $ openssl x509 -req -in ca.csr -CA root.crt -CAkey root.key -CAcreateserial -CAserial ca.srl -extfile v3_ca.ext -days 3650 -sha256 -out ca.crt 30 | 31 | 32 | #### Create CA chain file 33 | 34 | $ cat ca.crt root.crt > ca_chain.crt 35 | 36 | 37 | ### Server Certificates 38 | 39 | #### Create Server certificate key 40 | 41 | $ openssl genrsa -out server.key 4096 42 | 43 | 44 | #### Create Server certificate request 45 | 46 | $ openssl req -new -key server.key -out server.csr 47 | 48 | 49 | #### Sign Server certificate 50 | 51 | $ openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -CAserial server.srl -days 730 -sha256 -out server.crt 52 | 53 | 54 | #### Create Server Certificates with 'Subject Alternative Name' option 55 | 56 | > NOTE: `openssl x509` doesn't support creating certificates with SAN option well, 57 | > use `openssl ca` instead. 58 | 59 | $ openssl ca -in server.csr -config ../openssl.cnf -days 1460 -out server.crt 60 | 61 | #### Create dhparam.pem for TLS 62 | 63 | $ openssl dhparam -out dhparam.pem 2048 64 | 65 | 66 | ### Client certificates 67 | 68 | Similar to server certificates 69 | 70 | 71 | #### Create PKCS12 format certificates 72 | 73 | $ openssl pkcs12 -export -certfile ca_chain.crt -in client.crt -inkey client.key -out client.p12 74 | 75 | 76 | ## Verify certificates 77 | 78 | ### Check if certificate match CA 79 | 80 | $ openssl verify -verbose -CAfile ca.crt server.crt 81 | 82 | ### Check if certificate match private key 83 | 84 | $ openssl x509 -noout -modulus -in server.crt | openssl md5 85 | $ openssl rsa -noout -modulus -in server.key | openssl md5 86 | $ openssl req -noout -modulus -in server.csr | openssl md5 87 | 88 | ### Check SSL protocol 89 | 90 | $ openssl s_client -connect HOST:443 -${protocol} 91 | 92 | `${protocol}` candidates: tls1 tls1_1 tls1_2 tls1_3 ssl2 ssl3 93 | 94 | ### Check SSL Cipher 95 | 96 | $ openssl s_client -connect HOST:443 -cipher ${cipher} 97 | 98 | `${cipher}` candidates: ECDH RC4-SHA ... 99 | 100 | ### Check SNI 101 | 102 | $ openssl s_client -connect HOST:443 -servername ${sni_host} 103 | 104 | ### Check if client certificate matched 105 | 106 | $ openssl s_client -connect HOST:443 \ 107 | -cert client.crt \ 108 | -key client.key \ 109 | -state -debug 110 | 111 | 112 | ## Config file example 113 | 114 | ### v3_ca.ext 115 | 116 | basicConstraints = CA:TRUE 117 | subjectKeyIdentifier = hash 118 | authorityKeyIdentifier = keyid:always,issuer:always 119 | keyUsage = cRLSign, dataEncipherment, digitalSignature, keyCertSign, keyEncipherment, nonRepudiation 120 | 121 | 122 | ### openssl.cnf 123 | 124 | > NOTE: The 'subjectAltName' option is used to specify certificate's SAN option 125 | 126 | [ca] 127 | default_ca = CA_default # The default ca section 128 | 129 | [CA_default] 130 | dir = . # top dir 131 | database = $dir/index.txt # index file. 132 | new_certs_dir = $dir # new certs dir 133 | 134 | certificate = $dir/ca.crt # The CA cert 135 | serial = $dir/ca.srl # serial no file 136 | private_key = $dir/ca.key # CA private key 137 | RANDFILE = $dir/.rand # random number file 138 | 139 | default_days = 1460 # how long to certify for 140 | default_crl_days = 30 # how long before next CRL 141 | default_md = sha256 # md to use 142 | 143 | policy = policy_any # default policy 144 | email_in_dn = no # Don't add the email into cert DN 145 | x509_extensions = v3_req 146 | 147 | name_opt = ca_default # Subject name display option 148 | cert_opt = ca_default # Certificate display option 149 | copy_extensions = copy # Copy extensions from request 150 | 151 | [policy_any] 152 | countryName = supplied 153 | stateOrProvinceName = optional 154 | organizationName = optional 155 | organizationalUnitName = optional 156 | commonName = supplied 157 | emailAddress = optional 158 | 159 | 160 | [req] 161 | default_bits = 4096 162 | #default_keyfile = req.key 163 | #attributes = req_attributes 164 | 165 | distinguished_name = req_distinguished_name 166 | x509_extensions = v3_ca 167 | #req_extensions = v3_req 168 | default_md = sha256 169 | 170 | utf8 = yes 171 | dirstring_type = nobmp 172 | 173 | [req_distinguished_name] 174 | emailAddress = test@email.address 175 | countryName = Country Name (2 letter code) 176 | countryName_default = CN 177 | countryName_min = 2 178 | countryName_max = 2 179 | 180 | stateOrProvinceName = State or Province Name (full name) 181 | #stateOrProvinceName_default = Some-State 182 | 183 | localityName = Locality Name (eg, city) 184 | 185 | 0.organizationName = Organization Name (eg, company) 186 | #0.organizationName_default = Internet Widgits Pty Ltd 187 | 188 | # we can do this but it is not needed normally :-) 189 | #1.organizationName = Second Organization Name (eg, company) 190 | #1.organizationName_default = World Wide Web Pty Ltd 191 | 192 | organizationalUnitName = Organizational Unit Name (eg, section) 193 | #organizationalUnitName_default = 194 | 195 | commonName = Common Name (eg, YOUR name) 196 | commonName_max = 64 197 | 198 | emailAddress = Email Address 199 | emailAddress_max = 40 200 | 201 | [req_attributes] 202 | challengePassword = A challenge password 203 | challengePassword_min = 4 204 | challengePassword_max = 20 205 | 206 | 207 | [v3_ca] 208 | basicConstraints = CA:TRUE 209 | subjectKeyIdentifier = hash 210 | authorityKeyIdentifier = keyid:always,issuer:always 211 | keyUsage = cRLSign, dataEncipherment, digitalSignature, keyCertSign, keyEncipherment, nonRepudiation 212 | 213 | [v3_req] 214 | basicConstraints = CA:FALSE 215 | subjectKeyIdentifier = hash 216 | keyUsage = digitalSignature, keyEncipherment, nonRepudiation 217 | subjectAltName = @altNames 218 | 219 | 220 | [altNames] 221 | DNS.1 = example.com 222 | DNS.2 = *.example.com 223 | -------------------------------------------------------------------------------- /openstack/python-packaging.md: -------------------------------------------------------------------------------- 1 | # Python包管理工具小结 2 | 3 | - [Python包管理工具小结](#) 4 | - [setuptools](#) 5 | - [pbr](#) 6 | - [pip](#) 7 | - [virtualenv](#) 8 | - [wheel](#) 9 | - [Linux distros](#) 10 | - [总结](#) 11 | - [直接源码安装](#) 12 | - [virtualenv+源码直接安装](#) 13 | - [virtualenv+源码打包发布](#) 14 | - [使用Linux发行版本身的安装包](#) 15 | - [virtualenv+源码+Linux发行版安装包打包发布](#) 16 | - [References](#) 17 | 18 | 19 | 作为一名接触Python有一段时间的初学者,越来越体会到Python的方便之处,它使人能更 20 | 多的关注业务本身的逻辑,而不用太纠结语言层面的技巧与细节。然而,随着服务的规模 21 | 变得越来越大,如何方便快速地制作与发布一个Python软件包则越来越成为一个让人头疼 22 | 地问题,特别是像Openstack这种相对复杂、各种依赖也很多的Python项目,到目前也没有 23 | 发现特别完美的解决方案。这里将尝试对Python的包管理工具做一个小结,为将来更好的 24 | 解决这个问题提供思路。 25 | 26 | ## setuptools 27 | 28 | setuptools[1] 是Python提供的最基本的包管理工具,后面提到的其他更高层次的Python 29 | 包管理工具都是在它的基础上实现的。曾经Python还有其他各种各样类似功能的包管理工 30 | 具,比如distribute、distutils等等,对有选择困难症的同学非常不友好,不过目前绝大 31 | 多数Python项目都开始统一使用setuptools,其他工具已经被慢慢合并/遗弃了。 32 | 33 | setuptools的常见使用方式是在Python项目中新建一个`setup.py`: 34 | 35 | from setuptools import setup, find_packages 36 | setup( 37 | name = "HelloWorld", 38 | version = "0.1", 39 | packages = find_packages("helloworld"), 40 | ) 41 | 42 | 在这个文件中通过setuptools提供的各种关键字描述项目的元信息,包括名字、版本、 43 | 文件等等。写好后就可以通过`python setup.py [command]`执行setuptools提供的各种子 44 | 命令,比如编译、安装、测试、制作各种格式的软件安装包等等。 45 | 46 | 当你拿到一份Python项目的源代码,如果它已经写好了`setup.py`,你就可以方便的通过 47 | setuptools将它安装到系统: 48 | 49 | $ python setup.py install 50 | 51 | 目前,使用setuptools制作的软件安装包常见格式有: 52 | 53 | source distro 54 | 55 | $ python setup.py sdist 56 | 57 | binary distro: egg 58 | 59 | $ python setup.py bdist_egg 60 | 61 | binary distro: wheel 62 | 63 | $ python setup.py bdist_wheel 64 | 65 | 以openstack/nova为例,对应的三种格式软件安装包分别为: 66 | 67 | nova-12.0.0.0b2.dev251.tar.gz 68 | nova-12.0.0.0b2.dev251-py2.7.egg 69 | nova-12.0.0.0b2.dev251-py2.py3-none-any.whl 70 | 71 | sdist和wheel格式的安装包都可以使用最常见的pip命令安装,egg还只能通过 72 | 比较老得easy\_install命令安装。 73 | 74 | $ pip install nova-12.0.0.0b2.dev251.tar.gz 75 | $ easy_install nova-12.0.0.0b2.dev251-py2.7.egg 76 | $ pip install nova-12.0.0.0b2.dev251-py2.py3-none-any.whl 77 | 78 | ### pbr 79 | 80 | 从上面的例子里可以看到,`setup.py`本质上还是一个Python脚本,有诸多语法的限制。 81 | 当Python项目比较庞大和复杂时,`setup.py`就会变的非常难写与维护。 82 | 在Openstack中,新建了一个pbr[2] 项目来处理这个问题。通过使用pbr项目提供的维护与 83 | `setup.py`对应的`setup.cfg`文本配置文件来自动生成`setup.py`从而解决这个问题。 84 | 85 | 比如上面的例子,切换到pbr就会变成这样: 86 | 87 | setup.py 88 | 89 | from setuptools import setup 90 | 91 | setup( 92 | setup_requires=['pbr'], 93 | pbr=True, 94 | ) 95 | 96 | setup.cfg 97 | 98 | [metadata] 99 | name = HelloWorld 100 | version = 0.1 101 | 102 | [files] 103 | packages = 104 | helloworld 105 | 106 | 当然,实际工程中,`setup.cfg`文件的内容会比这个例子复杂很多。 107 | 108 | ## pip 109 | 110 | setuptools解决了Python软件包的制作问题,那如何分发它们呢?Python本身提供了PyPI 111 | 基础设施与相应的pip[3] 命令行工具来做这件事。 112 | 113 | 你可以将生成好的软件包通过 114 | 115 | $ python setup.py upload 116 | 117 | 通过网络上传到PyPI。 118 | 上传成功后,其他人就可以直接通过pip命令安装你上传的软件包: 119 | 120 | $ pip install ${package_name} 121 | 122 | ## virtualenv 123 | 124 | 提到了pip,就不得不提一下virtualenv[4] ,这两个工具经常是在一起搭配使用的。 125 | 126 | virtualenv的功能有点像Linux系统下的chroot命令,运行 127 | 128 | $ virtualenv .venv 129 | 130 | 后,他会将最小安装一个Python运行环境到当前目录的.venv文件夹,内容包括.venv/bin 131 | 下的python、pip命令,.venv/lib下的python库目录等等。当你使用 132 | 133 | $ . .venv/bin/activate 134 | 135 | 激活刚刚创建出来的这个virtualenv环境后,你执行的python相关的二进制命令其实都是 136 | .venv/bin目录下(而不是系统路径下的),安装/引用的python库也都是在.venv/lib目录 137 | 下的。 138 | 139 | 这样子,使用virtualenv后可以让每个Python项目使用独立的Python运行环境,不会产生 140 | 几个项目间依赖冲突、污染操作系统Python库的问题。 141 | 142 | ## wheel 143 | 144 | wheel[5] 是众多Python软件安装包格式的一种,这里专门要把它单独拿出来说是因为它的 145 | 优点和好处确实很多,是Python官方推荐的新一代Python项目发布格式,个人也非常希望 146 | 目前所有PyPI上项目都能尽快提供wheel格式的安装包。新版本的pip已经可以直接从PyPI 147 | 上下载安装wheel格式的安装包了。 148 | 149 | 和老的egg[6] 格式相比,wheel能让人直观感受到的先进之处: 150 | 151 | * 不包含.pyc文件(预编译的.pyc文件偶尔会导致各种奇怪的问题,我们更希望他能在 152 | 每次安装时在本地生成更新),当项目是纯Python项目时wheel和source distro基本 153 | 没有什么区别。 154 | * 官方支持,pip命令可以直接安装wheel,但是不能处理egg。 155 | * 更丰富的软件包元信息,甚至包中的每个文件都有版本记录。 156 | * 更细致的包命名规则 157 | 158 | 和egg格式一样,wheel拥有的优点: 159 | 160 | * 单安装文件。你是希望从源码文件夹下执行一系列命令编译安装一个Python项目,还是 161 | 直接通过一个文件安装呢? 162 | * 依赖处理。安装程序会帮你自动安装相关依赖的Python库。 163 | * 二进制发布格式。当项目包含需要编译生成的extension时,可以将编译好的 164 | .so/.dylib/.dll等动态链接库直接一并打包,实现“一次编译,到处运行”(在相同的 165 | 平台上)的效果,而不是每次都在终端重新编译生成。这点在大规模服务器上批量部署 166 | Python程序项目非常重要。可惜的是,目前wheel仅在windows和mac os平台支持这个特 167 | 性,Linux平台由于各种原因还不支持,希望PyPA能尽快解决吧。 168 | 169 | 这里有一份官方文档详细比较了wheel和egg: [7] 170 | 171 | ## Linux distros 172 | 173 | 上面总结了Python打包社区PyPA(Python Packaging Authority)本身提供的各种工具, 174 | 但是在各个Linux发行版中,出于和系统集成(比如各种配置文件、脚本的管理)、加强 175 | 包的管理(PyPI上的软件包都是开发者自行自由上传的)等等原因,很多Linux发行版通常 176 | 也会制作维护各自平台上专有的Python项目安装包,比如Debian系的.deb格式安装包 177 | 等等。 178 | 179 | 由于各个Linux发行版的管理风格、系统路径、甚至默认的Python版本等等都大相径庭, 180 | 所以各种安装包的规格、内容也差别很大。 181 | 182 | 常见的Linux发行版里,Debian系的.deb格式和Redhat系的.rpm格式安装包属于比较严格和 183 | 保守的一派。它们通常会做严格的版本、依赖控制;维护项目相关的服务配置文件、 184 | SysVinit/systemd、syslog、logrotate脚本等等;安装包会包含任何需要编译的二进制 185 | 文件(如.pyc文件、各种需要编译的c extension等),不需要在安装时进行本地编译; 186 | 使用Python2作为Python的默认版本,甚至在某些较老的Debian、CentOS的发行版中, 187 | 还在使用Python2.6/2.3。 188 | 189 | 而像ArchLinux这样比较激进的Linux发行版,安装包的管理就非常奔放。安装包通常只是 190 | 帮你指定一下依赖、任何软件和库都用最新版本(非常令人讨厌的,Python也默认使用 191 | Python3)、Python项目的`PKGBUILD`里package()通常就一行 192 | “python2 setup.py install ...”。 193 | 194 | ## 总结 195 | 196 | 上面基本把最近自己接触到的和Python相关的包管理工具介绍了一遍,而在实际操作中去 197 | 在服务器上批量发布部署Python项目,利用上面提到的这些工具,能想到的通常有下面 198 | 几种方式。 199 | 200 | 每次到这个时候,真的是非常羡慕Java/Go这些不用纠结这种问题的编程语言。 201 | 202 | ### 直接源码安装 203 | 204 | 适合在自己的开发调试机器上瞎搞的时候使用。 205 | 206 | 由于Python/pip本身的包管理功能比较弱,直接在服务器上用这种方法会给服务器的系统 207 | 安装一系列不可控制的Python依赖,并且可能破坏其他Python程序的依赖,导致其他 208 | Python程序无法正常运行。装的时候方便,当你碰到问题想清理的时候可就蛋疼了,相信 209 | 维护服务器的SA也不太可能会让你这样做的。 210 | 211 | ### virtualenv+源码直接安装 212 | 213 | 比较靠谱的办法,但是只适合单机部署。当服务器数量众多,如何实现批量部署发布就是 214 | 个困难的问题了。 215 | 216 | ### virtualenv+源码打包发布 217 | 218 | 在virtualenv环境里安装好所有的Python文件,把整个virtualenv目录打包成一个单独的 219 | tar包。发布时直接将这个tar包拷贝到目标服务器上解压安装。 220 | 221 | 目前一些知名的Openstack厂商,如Rackspace,就是通过这种方式在服务器上部署Python 222 | 项目的。 223 | 224 | 然而,这样做的缺点是,还需要做很多额外的工作去维护服务的配置文件、脚本、系统上 225 | 非Python相关的软件依赖(如kvm、libvirt、openvswitch)等等。 226 | 227 | 类似方法还有自建私有PyPI源,提前将Python项目打包上传到自建的PyPI源里。然后在 228 | 服务器上的virtualenv环境里通过自建的PyPI源安装。 229 | 230 | ### 使用Linux发行版本身的安装包 231 | 232 | 比如,Debian系的.deb安装包、Redhat系的.rpm安装包。它们能比较好的维护各种服务 233 | 配置文件、脚本,以及像kvm这样的非Python相关的依赖,我们目前也在使用这种方式。 234 | 235 | 然而,它也有很多问题,以我比较熟悉的Debian发行版为例: 236 | 237 | * 所有Python项目共用一套Python依赖。当有的Python项目想升级某个第三方库时,会 238 | 因为破坏其他Python项目的依赖版本而无法实现。 239 | * 官方提供的软件版本比较旧。对于一个追求稳定的Linux发行版,这么做本身并无可厚 240 | 非,但是有时候当你就是想用新一点版本时,就会感觉非常无奈,基本都要自己动手制 241 | 作安装包。而且由于依赖的关系,可能你为了给一个软件制作新版本的安装包,需要再 242 | 为其他10个依赖的项目制作新版本的安装包,然后又要为这10个依赖的项目再制作30个 243 | 依赖的依赖的新版本安装包。。。 244 | * 学习成本比较高。Debian的安装包制作方法,除了官方十年前写的几份文档外,网上 245 | 其他能找到的资料很少,学起来比较费力。Debian作为一个装机量较大的Linux发行版都 246 | 是这样,相信其他发行版也是类似的情况。 247 | 248 | ### virtualenv+源码+Linux发行版安装包打包发布 249 | 250 | 在virtualenv环境里安装好所有的Python文件,然后将整个virtualenv目录打包到单独的 251 | 一个Linux发行版的安装包内,同时在这个安装包内设置好相关的配置文件、脚本、系统 252 | 依赖。 253 | 254 | 理论上这种方法综合了上面两种方式的优点,目前正在调研中,然而相信实践过程中肯定 255 | 会踩到不少坑。 256 | 257 | spotify公布了他们在Debian下使用这种方式解决Python项目发布部署问题的工具 258 | `dh-virtualenv`[8] ,感兴趣的同学可以参考一下。 259 | 260 | ## References 261 | 262 | [1]: http://pythonhosted.org/setuptools/index.html 263 | [2]: http://docs.openstack.org/developer/pbr 264 | [3]: https://pip.pypa.io/en/stable 265 | [4]: https://virtualenv.pypa.io/en/latest 266 | [5]: https://www.python.org/dev/peps/pep-0427 267 | [6]: http://peak.telecommunity.com/DevCenter/PythonEggs 268 | [7]: https://packaging.python.org/en/latest/wheel_egg.html 269 | [8]: https://github.com/spotify/dh-virtualenv 270 | 271 | * [Python Packaging User Guide](https://packaging.python.org/en/latest/index.html) 272 | * [Python on Wheels](http://lucumr.pocoo.org/2014/1/27/python-on-wheels) 273 | * [Python 包管理工具解惑](http://zengrong.net/post/2169.htm) 274 | -------------------------------------------------------------------------------- /openstack/inside-nova-periodic-task.md: -------------------------------------------------------------------------------- 1 | ## nova中定时任务(periodic_task)原理分析 2 | 3 | 在nova源代码中, 可以在很多函数上看到`@periodic_task`这样的修饰符, 我们知道这是nova的定时任务, 4 | 可以让这个函数周期性执行, 但是可能不太了解这个修饰符产生作用的原理和用法, 这里将详细说明一下. 5 | 6 | 7 | ### decorator `nova.manager.periodic_task` 8 | 9 | ```python 10 | # in nova.manager 11 | def periodic_task(*args, **kwargs): 12 | def decorator(f): 13 | f._periodic_task = True 14 | f._ticks_between_runs = kwargs.pop('ticks_between_runs', 0) 15 | return f 16 | 17 | if kwargs: 18 | return decorator 19 | else: 20 | return decorator(args[0]) 21 | ``` 22 | 23 | 可以看到`@periodic_task`其实只是给被修饰的函数加上了`_periodic_task`和`_ticks_between_runs` 24 | 2个attr, 并没有做其他的操作. 周期执行函数的action实际上是`nova.manager.Manager`和`nova.utils.LoopingCall` 25 | 配合实现的, 后面将详细说明. 26 | 27 | 28 | ### black magic of `nova.manager.ManagerMeta` 29 | 30 | ```python 31 | # in nova.manager 32 | class ManagerMeta(type): 33 | def __init__(cls, names, bases, dict_): 34 | super(ManagerMeta, cls).__init__(names, bases, dict_) 35 | 36 | try: 37 | cls._periodic_tasks = cls._periodic_tasks[:] 38 | except AttributeError: 39 | cls._periodic_tasks = [] 40 | 41 | try: 42 | cls._ticks_to_skip = cls._ticks_to_skip.copy() 43 | except AttributeError: 44 | cls._ticks_to_skip = {} 45 | 46 | for value in cls.__dict__.values(): 47 | if getattr(value, '_periodic_task', False): 48 | task = value 49 | name = task.__name__ 50 | cls._periodic_tasks.append((name, task)) 51 | cls._ticks_to_skip[name] = task._ticks_between_runs 52 | ``` 53 | 54 | nova.manager中的`ManagerMeta`类是`nova.manager.Manager`的metaclass(在后面可以看到), 这里可以 55 | 简单的认为`ManagerMeta`是`Manager`的父类. 在`nova.manager.Manager`初始化时, 会调用`ManagerMeta`的 56 | `__init__()`方法. 57 | 58 | (metaclass属于python里的黑魔法内容, 这里不做详细说明, 大法师们感兴趣可以去看看官方手册 59 | http://docs.python.org/reference/datamodel.html#customizing-class-creation) 60 | 61 | for value in cls.__dict__.values(): 62 | if getattr(value, '_periodic_task', False): 63 | 64 | `ManagerMeta.__init__()`中的这两行会将cls(也就是`Manager`对象自己, 注意是 **Manager对象** 不是`Manager`的实例) 65 | 中所有使用过`@periodic_task`修饰符修饰的 **函数对象** 过滤出来. 66 | 过滤后会将这些函数对象放入`Manager._periodic_tasks`中, 后面定时任务的实现都是从这个变量里取出函数对象并执行. 67 | 68 | 69 | ### `nova.manager.Manager.periodic_tasks` 70 | (注意和`nova.manager.periodic_task`的区别) 71 | 72 | ```python 73 | class Manager(base.Base): 74 | __metaclass__ = ManagerMeta 75 | 76 | def periodic_tasks(self, context, raise_on_error=False): 77 | for task_name, task in self._periodic_tasks: 78 | full_task_name = '.'.join([self.__class__.__name__, task_name]) 79 | 80 | ticks_to_skip = self._ticks_to_skip[task_name] 81 | if ticks_to_skip > 0: 82 | LOG.debug(_("Skipping %(full_task_name)s, %(ticks_to_skip)s" 83 | " ticks left until next run"), locals()) 84 | self._ticks_to_skip[task_name] -= 1 85 | continue 86 | 87 | self._ticks_to_skip[task_name] = task._ticks_between_runs 88 | LOG.debug(_("Running periodic task %(full_task_name)s"), locals()) 89 | 90 | try: 91 | task(self, context) 92 | except Exception as e: 93 | if raise_on_error: 94 | raise 95 | LOG.exception(_("Error during %(full_task_name)s: %(e)s"), 96 | locals()) 97 | ``` 98 | 99 | 从前面`ManagerMeta`的说明我们已经知道`Manager`对象建立时, 会将所有attr `_periodic_task`为 **True** 的函数对象 100 | 放入`self._periodic_tasks`中. 101 | 102 | 在`Manager`中, 我们可以发现一个和`periodic_task`十分相似的函数`periodic_tasks`, 通过阅读函数代码可以发现 103 | 这个函数实际上的作用就是把所有在`self._periodic_tasks`中的函数对象(也就是所有用`@periodic_task`修饰符修饰过的函数) 104 | 全部遍历并调用一遍. 如果能定期调用这个函数的话, 就能实现类似linux中crontab的定时任务功能. 105 | 106 | 下面将说明nova如何定时调用`nova.manager.Manager.periodic_tasks`实现定时任务. 107 | 108 | 109 | ### `nova.utils.LoopingCall` 110 | 111 | ```python 112 | class LoopingCall(object): 113 | def __init__(self, f=None, *args, **kw): 114 | self.args = args 115 | self.kw = kw 116 | self.f = f 117 | self._running = False 118 | 119 | def start(self, interval, now=True): 120 | self._running = True 121 | done = event.Event() 122 | 123 | def _inner(): 124 | if not now: 125 | greenthread.sleep(interval) 126 | try: 127 | while self._running: 128 | self.f(*self.args, **self.kw) 129 | if not self._running: 130 | break 131 | greenthread.sleep(interval) 132 | except LoopingCallDone, e: 133 | self.stop() 134 | done.send(e.retvalue) 135 | except Exception: 136 | LOG.exception(_('in looping call')) 137 | done.send_exception(*sys.exc_info()) 138 | return 139 | else: 140 | done.send(True) 141 | 142 | self.done = done 143 | 144 | greenthread.spawn(_inner) 145 | return self.done 146 | 147 | def stop(self): 148 | self._running = False 149 | 150 | def wait(self): 151 | return self.done.wait() 152 | ``` 153 | 154 | `nova.utils.LoopingCall`的作用就是实现前面提到的定时调用函数的功能. 155 | 156 | 将函数对象作为`LoopingCall`的第一个构造参数传入构造一个`LoopingCall`对象, 然后调用其`start()`方法后 157 | 调用其`wait()`方法, 就可以实现定时执行函数的功能. 158 | 159 | `start`方法的`interval`参数为函数两次执行期间的时间间隔, 单位为秒. 前面提到的`@periodic_task`修饰符 160 | 可以设置一个参数`ticks_between_runs`, 是与其配合使用的, 指经过几次ticks才执行函数. 比如, 上下文为 161 | `@periodic_task(ticks_between_runs=2)`并且`interval=5`的话, 被修饰的函数将每 (2+1)*5=15 seconds 执行一次 162 | 163 | 如果被修饰的函数里`raise nova.utils.LoopingCallDone`, 可以让LoopingCall的定时任务close gracefully. 164 | 165 | 166 | ## Examples 167 | 168 | 下面两个例子演示了如何使用`nova.manager`中的`@periodic_task`修饰符, 或直接使用`nova.utils.LoopingCall` 169 | 在nova中实现定时任务. 170 | 171 | 172 | 173 | ### periodic_task.py 174 | 175 | [[raw file|http://netease.stanzgy.org/~stanzgy/script/periodic_task/periodic_task.py]] 176 | 177 | ```python 178 | #!/usr/bin/env python2 179 | 180 | from nova import manager 181 | from nova import utils 182 | import time 183 | import inspect 184 | import eventlet 185 | 186 | class TestManager(manager.Manager): 187 | def __init__(self, *args, **kwargs): 188 | self.count = 0 189 | super(TestManager, self).__init__(*args, **kwargs) 190 | 191 | @manager.periodic_task 192 | def foooooo(self, context=None): 193 | self.count += 1 194 | print "Just you know why" 195 | #print time.ctime() 196 | 197 | @manager.periodic_task 198 | def baaaaar(self, context=None): 199 | self.count += 1 200 | print "Panda" 201 | #print time.ctime() 202 | 203 | @manager.periodic_task(ticks_between_runs=3) 204 | def panda(self, context=None): 205 | print "Panda. Mae teleheshka." 206 | 207 | 208 | test_manager = TestManager() 209 | periodic = utils.LoopingCall(test_manager.periodic_tasks, context=None) 210 | periodic.start(interval=0.8753) 211 | periodic.wait() 212 | ``` 213 | 214 | Output: 215 | 216 | stanzgy % python periodic_task.py 217 | Just you know why 218 | Panda 219 | Just you know why 220 | Panda 221 | Just you know why 222 | Panda 223 | Just you know why 224 | Panda 225 | Panda. Mae teleheshka. 226 | Just you know why 227 | Panda 228 | Just you know why 229 | Panda 230 | Just you know why 231 | Panda 232 | Just you know why 233 | Panda 234 | Panda. Mae teleheshka. 235 | ... 236 | 237 | 238 | ### periodic_func.py 239 | 240 | [[raw file|http://netease.stanzgy.org/~stanzgy/script/periodic_task/periodic_func.py]] 241 | 242 | ```python 243 | #!/usr/bin/env python2 244 | 245 | from nova import utils 246 | import inspect 247 | import eventlet 248 | 249 | global count 250 | count=0 251 | 252 | def panda(tagline): 253 | global count 254 | count += 1 255 | print "#", count, "Panda.", tagline 256 | if count >= 9: 257 | raise utils.LoopingCallDone 258 | 259 | periodic = utils.LoopingCall(panda, "Mae teleheshka.") 260 | periodic.start(interval=0.8753) 261 | periodic.wait() 262 | ``` 263 | 264 | Output: 265 | 266 | stanzgy % python periodic_func.py 267 | # 1 Panda. Mae teleheshka. 268 | # 2 Panda. Mae teleheshka. 269 | # 3 Panda. Mae teleheshka. 270 | # 4 Panda. Mae teleheshka. 271 | # 5 Panda. Mae teleheshka. 272 | # 6 Panda. Mae teleheshka. 273 | # 7 Panda. Mae teleheshka. 274 | # 8 Panda. Mae teleheshka. 275 | # 9 Panda. Mae teleheshka. 276 | -------------------------------------------------------------------------------- /markup/asciidoc-guide.asciidoc: -------------------------------------------------------------------------------- 1 | = AsciiDoc简明指南 = 2 | Jasen 3 | 4 | 5 | AsciiDoc是一种文本文档格式,可以用于书写文档,文章,手册,书籍和UNIX手册。AsciiDoc文件可以使用asciidoc命令转换成HTML和DocBook文件格式。AsciiDoc结构先进:AsciiDoc语法和输出标签(几乎可以转换成任意的SGML/XML标记)都可以由用户自己定义和扩展。 6 | 7 | == 特殊定义 == 8 | 9 | 变量形式 \{变量} 10 | 11 | 参数形式 [参数组] 12 | 13 | 转义符号 \ 14 | 15 | 注释 //空格 16 | 17 | 引用 空格后面跟文字 18 | 19 | == 基本区块 == 20 | 基本区块是一行或多行文本,也可以包括其他基本区块。 21 | 22 | AsciiDoc基本区块概括如下[这是一个大概的结构指南,不是严格的语法定义]: 23 | 24 | 文档 ::= (头部?,前言?,章节*) 25 | 头部 ::= (标题,(作者,修订?)?) 26 | 作者 ::= (名字,(中间名?,姓氏)?,Email地址?) 27 | 修订 ::= (版本?,日期) 28 | 前言 ::= (段落) 29 | 章节 ::= (标题,段落?,(章节)*) 30 | 段落 ::= ((区块标题?,区块)|宏块)+ 31 | 区块 ::= (自然段|独立块|列表|表格) 32 | 列表 ::= (无序列表|有序列表|定义列表|标注列表) 33 | 无序列表 ::= (项目)+ 34 | 有序列表 ::= (项目)+ 35 | 标注列表 ::= (项目)+ 36 | 定义列表 ::= (条目)+ 37 | 条目 ::= (标签,项目) 38 | 标签 ::= (项目+) 39 | 项目 ::= (项目文本,(列表|段落列表|列表后续)*) 40 | 41 | 注释: 42 | 43 | - '?' 表示0或1个,'+' 表示0或多个,'*' 表示1或多个。 44 | - 所有的基本区块有独立的界线分开. 45 | - 下面的基本部分不能包含空行: 头部,标题,段落,项目文本。 46 | 47 | 48 | == 头部 == 49 | 头部包含文档标题、作者和版本信息。头部可选但是必须在文档的顶部。 50 | 51 | = 使用 AsciiDoc 编写文档 = 52 | Joe Bloggs 53 | v2.0, February 2003: 54 | Rewritten for version 2 release. 55 | 56 | == 标签 == 57 | 58 | === 标题 === 59 | 需要靠左边顶格 60 | 61 | = 文档标题 (0级) = 62 | == 段落标题 (1级) == 63 | === 段落标题 (2级) === 64 | ==== 段落标题 (3级) ==== 65 | ===== 段落标题 (4级) ===== 66 | 67 | .段落,列表,表格或区块标题 68 | 69 | === 文字格式 === 70 | 下面的格式标签前后要有空格,没有空格的时候需要是双符号。 71 | 72 | \_强调_ _强调_ 73 | 74 | \'强调' '强调' 75 | 76 | \*粗体* *粗体* 77 | 78 | \+等宽字体+ +等宽字体+ 79 | 80 | \`等宽字体` `等宽字体` 里面的格式命令无效 81 | 82 | \`单引号引用' `单引号引用' 83 | 84 | \\``双引号引用'' ``双引号引用'' 85 | 86 | \#无格式文本# #无格式文本# 里面的格式命令无效 87 | 88 | [字体CSS颜色,背景CSS颜色,文字em大小] 使用在格式前面可以显示颜色和大小可以使用[,,2]的形式 89 | 90 | \^上标\^ ^上标^ 91 | 92 | \~下标\~ ~下标~ 93 | 94 | === 特殊符号 === 95 | \(C) (C) 版权 96 | 97 | \(TM) (TM) 商标 98 | 99 | \(R) (R) 注册商标 100 | 101 | \-- -- 破折号 102 | 103 | \... ...省略号 104 | 105 | \-> -> 右箭头 106 | 107 | \<- <- 左箭头 108 | 109 | \=> => 右双箭头 110 | 111 | \<= <= 左双箭头 112 | 113 | \➊ ➊ 114 | 115 | \¶ ¶ 116 | 117 | === 文本块样式 === 118 | 119 | 120 | //// 121 | CommentBlock(注释块)不输出到目标文件 122 | //// 123 | 124 | ++++ 125 | PassthroughBlock,不转换内部的特殊字符 126 | ++++ 127 | 128 | ---- 129 | ListingBlock(清单块) 130 | 用于计算机的输出和文件列表。也可用于程序代码。里面特殊字符不替换。 131 | ---- 132 | 133 | .... 134 | LiteralBlock(文字块) 135 | 就像普通文字段落,保留了空格,使用[listing]块标题可以在内部引用其他块样式。 136 | .... 137 | 138 | **** 139 | SidebarBlock(侧边栏) 140 | 有边框和背景 141 | **** 142 | 143 | ____ 144 | QuoteBlock(引用块) 145 | ____ 146 | 147 | ==== 148 | ExampleBlock(例子块) 149 | 会使用数字编号的例子标题[caption="例1: "] 150 | 可以使用NOTE, TIP, IMPORTANT, WARNING, CAUTION标题[NOTE]表示提示块 151 | ==== 152 | 153 | -- 154 | OpenBlock(开放块) 155 | -- 156 | 157 | === 提示段落 === 158 | 'Tip'提示, 'Note'注意, 'Important'重要, 'Warning'警告 和 'Caution'警示。例子: 159 | 160 | NOTE: 这是一个注意的例子。 161 | 162 | 或使用替代语法: 163 | 164 | [NOTE] 165 | 这是一个注意的例子。 166 | 167 | [icons=None, caption="提示"] 168 | TIP: 如果警告多于一个段落使用警告段落替代. 169 | 170 | ==== 警告图标和标题 ==== 171 | [icons=None, caption="注意"] 172 | NOTE: 警告可以定制 `icons`, `iconsdir`, `icon` 和 `caption` 参数。 173 | 174 | 默认生成文本标题替换图标链接,生成图标链接需要使用`-a icons`命令行参数。 175 | 你可以使用`icon`参数制定图标的路径。例如: 176 | 177 | [icon="./images/icons/wink.png"] 178 | NOTE: What lovely war. 179 | 180 | 使用 `caption` 参数定义警告标题下例屏蔽图标并定义了注释警告的标题(在警告图标设置为可用时`icons` 参数必须设置为`icons=None`): 181 | 182 | [icons=None, caption="特殊提示"] 183 | NOTE: This is my special note. 184 | 185 | 186 | 187 | 188 | === 列表 === 189 | 190 | ==== 无序列表 ==== 191 | .... 192 | - List item. 193 | * List item. 194 | ** List item. 195 | *** List item. 196 | **** List item. 197 | ***** List item. 198 | .... 199 | 200 | ==== 有序列表 ==== 201 | 202 | 手动序号序号使用下面一种 203 | 204 | .... 205 | 1. 阿拉伯数字标注的列表项目. 206 | a. 小写字母标注的列表项目. 207 | F. 大写字母标注的列表项目. 208 | iii) 小写罗马数字标注的列表项目. 209 | IX) 大写罗马数字标注的列表项目. 210 | .... 211 | 212 | 自动符号列表项目是1-5个句点,跟着是一些空格和文本。句点个数表示级别。例如: 213 | .... 214 | . 阿拉伯数字标注的列表项目. 215 | .. 小写字母标注的列表项目. 216 | ... 小写罗马数字标注的列表项目. 217 | .... 大写字母标注的列表项目. 218 | ..... 大写罗马数字标注的列表项目. 219 | .... 220 | 221 | 222 | ==== 定义列表 ==== 223 | 224 | 字母或数字开始1-4个冒号或两个分号结束。 225 | 226 | --------------------------------------------------------------------- 227 | 项目1:: 228 | 项目2:: 229 | 项目说明. 230 | 231 | 项目说明. 232 | 233 | 项目3:: 项目说明. 234 | * 列表项. 235 | * 列表项. 236 | 项目4:: 237 | 说明1. 238 | 二级项目;; 239 | 二级说明. 240 | 二级项目;; 241 | 二级说明. 242 | 二级项目;; 243 | 二级说明. 244 | 三级项目::: 三级说明. 245 | 三级项目::: 三级说明. 246 | *水平项目*:: 水平说明. 247 | 248 | 水平说明. 249 | 250 | --------------------------------------------------------------------- 251 | 252 | ==== 问答列表 ==== 253 | --------------------------------------------------------------------- 254 | [qanda] 255 | 问题1:: 256 | 答案1. 257 | 问题2:: 258 | 答案2. 259 | --------------------------------------------------------------------- 260 | 261 | ==== 专业术语列表 ==== 262 | --------------------------------------------------------------------- 263 | [glossary] 264 | 术语1:: 265 | 解释1. 266 | 术语2:: 267 | 解释2. 268 | --------------------------------------------------------------------- 269 | 270 | === 脚注 === 271 | 272 | A footnote footnote:[An example footnote.]; 273 | a second footnote with a reference ID footnoteref:[note2,Second footnote.]; 274 | finally a reference to the second footnote footnoteref:[note2]. 275 | 276 | 277 | === 超链接 === 278 | 'http','https','ftp','file','mailto'和'callto'超链接。直接书写就行,参数是显示名称,空格使用%20。 279 | 280 | http://www.methods.co.nz/asciidoc/[The AsciiDoc home page] 281 | http://www.methods.co.nz/asciidoc/ 282 | mailto:joe.bloggs@foobar.com[email Joe Bloggs] 283 | joe.bloggs@foobar.com 284 | 285 | === 锚点 === 286 | 287 | 锚点:[[A88]] 288 | 289 | 链接:<> 290 | 291 | === 图片 === 292 | image:images/logo.png["Company Logo",height=32,link="screen.png",scaledwidth="75%"align="left"] 293 | 294 | 295 | 296 | === 表格 === 297 | 298 | .... 299 | [width="40%",cols="^,2m",frame="topbot",options="header,footer"] 300 | |====================== 301 | |Column 1 |Column 2 302 | |1 |Item 1 303 | |2 |Item 2 304 | |3 |Item 3 305 | |6 |Three items 306 | |====================== 307 | .... 308 | 309 | 310 | 表格参数: 311 | 312 | frame边框topbot(上和下),all(全部边,默认), none and sides (左和右)。 313 | align对齐left,right和center。 314 | options选项header(有标题)footer(有底部) 315 | width宽度1-99% 316 | 317 | 行参数: 318 | 319 | [*][][][