Ansible playbook 执行流程

背景

超时主要分两个场景,一个是建立 SSH 连接时,另外一个为通过 SSH 执行命令时的超时。前者主要依靠 SSH 命令的自带参数 ConnectTimeout,后者则有一套 Ansible 自己的实现。

建立连接超时

ansible 2 -u root -m ping -T 30。底层实现使用 ssh -o ConnectTimeout=30

执行命令超时(playbook)

在 playbook 的 task 中加入 asyncpoll 属性。

  • async 表示这个step的最长等待时长, 如果设置为0, 表示一直等待下去直到动作完成;
  • poll 表示检查step操作结果的间隔时长,poll 设置为0, 表示不用等待执行结果, 该step执行成功。

测试用的 playbook 如下:

1
2
3
4
5
6
7
8
9
10
11
12
---
- hosts: "1"
vars:
http_port: 80
max_clients: 200
remote_user: root
tasks:
- name: Sleep30s
shell:
"sleep 30s"
async: 10
poll: 1

启动命令如下:

1
ansible-playbook pb.yml

效果:

执行命令超时(playbook)的流程

在此 playbook 中,有一个 task,但是在执行上面定义的 task 之前,会执行一个 Gathering Facts 的 task,这里将此不太相干的 task 去掉了。task Sleep30s 的执行日志如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
TASK [Sleep30s] ****************************************************************
task path: /Users/yangyu/pb.yml:8
<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'echo ~root && sleep 0'"'"''
<9.134.124.159> (0, b'/root\n', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo /root/.ansible/tmp `"&& mkdir "` echo /root/.ansible/tmp/ansible-tmp-1600333356.414965-42861-191307013081145 `" && echo ansible-tmp-1600333356.414965-42861-191307013081145="` echo /root/.ansible/tmp/ansible-tmp-1600333356.414965-42861-191307013081145 `" ) && sleep 0'"'"''
<9.134.124.159> (0, b'ansible-tmp-1600333356.414965-42861-191307013081145=/root/.ansible/tmp/ansible-tmp-1600333356.414965-42861-191307013081145\n', b'')

Using module file /Users/yangyu/projects/ansible/lib/ansible/modules/command.py
<9.134.124.159> PUT /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmp_66m3nen TO /root/.ansible/tmp/ansible-tmp-1600333356.414965-42861-191307013081145/AnsiballZ_command.py
<9.134.124.159> SSH: EXEC sftp -b - -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 '[9.134.124.159]'
<9.134.124.159> (0, b'sftp> put /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmp_66m3nen /root/.ansible/tmp/ansible-tmp-1600333356.414965-42861-191307013081145/AnsiballZ_command.py\n', b'')


<9.134.124.159> PUT /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmplmizhdvg TO /root/.ansible/tmp/ansible-tmp-1600333356.414965-42861-191307013081145/async_wrapper.py
<9.134.124.159> SSH: EXEC sftp -b - -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 '[9.134.124.159]'
<9.134.124.159> (0, b'sftp> put /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmplmizhdvg /root/.ansible/tmp/ansible-tmp-1600333356.414965-42861-191307013081145/async_wrapper.py\n', b'')


<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'chmod u+x /root/.ansible/tmp/ansible-tmp-1600333356.414965-42861-191307013081145/ /root/.ansible/tmp/ansible-tmp-1600333356.414965-42861-191307013081145/AnsiballZ_command.py /root/.ansible/tmp/ansible-tmp-1600333356.414965-42861-191307013081145/async_wrapper.py && sleep 0'"'"''
<9.134.124.159> (0, b'', b'')


<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 -tt 9.134.124.159 '/bin/sh -c '"'"'ANSIBLE_ASYNC_DIR='"'"'"'"'"'"'"'"'~/.ansible_async'"'"'"'"'"'"'"'"' /usr/bin/python /root/.ansible/tmp/ansible-tmp-1600333356.414965-42861-191307013081145/async_wrapper.py 48991476669 10 /root/.ansible/tmp/ansible-tmp-1600333356.414965-42861-191307013081145/AnsiballZ_command.py _ && sleep 0'"'"''
<9.134.124.159> (0, b'{"started": 1, "_ansible_suppress_tmpdir_delete": true, "finished": 0, "results_file": "/root/.ansible_async/48991476669.20840", "ansible_job_id": "48991476669.20840"}\r\n', b'Shared connection to 9.134.124.159 closed.\r\n')


<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'echo ~root && sleep 0'"'"''
<9.134.124.159> (0, b'/root\n', b'')


<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo /root/.ansible/tmp `"&& mkdir "` echo /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103 `" && echo ansible-tmp-1600333358.847193-42861-146017266031103="` echo /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103 `" ) && sleep 0'"'"''
<9.134.124.159> (0, b'ansible-tmp-1600333358.847193-42861-146017266031103=/root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103\n', b'')


Using module file /Users/yangyu/projects/ansible/lib/ansible/modules/async_status.py
<9.134.124.159> PUT /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmpgapz8kyp TO /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py
<9.134.124.159> SSH: EXEC sftp -b - -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 '[9.134.124.159]'
<9.134.124.159> (0, b'sftp> put /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmpgapz8kyp /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py\n', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'chmod u+x /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/ /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py && sleep 0'"'"''
<9.134.124.159> (0, b'', b'')


<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 -tt 9.134.124.159 '/bin/sh -c '"'"'/usr/bin/python /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py && sleep 0'"'"''
<9.134.124.159> (0, b'\r\n{"started": 1, "invocation": {"module_args": {"jid": "48991476669.20840", "mode": "status", "_async_dir": "/root/.ansible_async"}}, "finished": 0, "ansible_job_id": "48991476669.20840"}\r\n', b'Shared connection to 9.134.124.159 closed.\r\n')
ASYNC POLL on 9.134.124.159: jid=48991476669.20840 started=1 finished=0


Using module file /Users/yangyu/projects/ansible/lib/ansible/modules/async_status.py
<9.134.124.159> PUT /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmpph94rjs7 TO /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py
<9.134.124.159> SSH: EXEC sftp -b - -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 '[9.134.124.159]'
<9.134.124.159> (0, b'sftp> put /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmpph94rjs7 /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py\n', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'chmod u+x /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/ /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py && sleep 0'"'"''
<9.134.124.159> (0, b'', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 -tt 9.134.124.159 '/bin/sh -c '"'"'/usr/bin/python /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py && sleep 0'"'"''
<9.134.124.159> (0, b'\r\n{"started": 1, "invocation": {"module_args": {"jid": "48991476669.20840", "mode": "status", "_async_dir": "/root/.ansible_async"}}, "finished": 0, "ansible_job_id": "48991476669.20840"}\r\n', b'Shared connection to 9.134.124.159 closed.\r\n')
ASYNC POLL on 9.134.124.159: jid=48991476669.20840 started=1 finished=0


Using module file /Users/yangyu/projects/ansible/lib/ansible/modules/async_status.py
<9.134.124.159> PUT /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmpia9_huhw TO /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py
<9.134.124.159> SSH: EXEC sftp -b - -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 '[9.134.124.159]'
<9.134.124.159> (0, b'sftp> put /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmpia9_huhw /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py\n', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'chmod u+x /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/ /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py && sleep 0'"'"''
<9.134.124.159> (0, b'', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 -tt 9.134.124.159 '/bin/sh -c '"'"'/usr/bin/python /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py && sleep 0'"'"''
<9.134.124.159> (0, b'\r\n{"started": 1, "invocation": {"module_args": {"jid": "48991476669.20840", "mode": "status", "_async_dir": "/root/.ansible_async"}}, "finished": 0, "ansible_job_id": "48991476669.20840"}\r\n', b'Shared connection to 9.134.124.159 closed.\r\n')
ASYNC POLL on 9.134.124.159: jid=48991476669.20840 started=1 finished=0


Using module file /Users/yangyu/projects/ansible/lib/ansible/modules/async_status.py
<9.134.124.159> PUT /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmp0dw1l581 TO /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py
<9.134.124.159> SSH: EXEC sftp -b - -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 '[9.134.124.159]'
<9.134.124.159> (0, b'sftp> put /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmp0dw1l581 /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py\n', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'chmod u+x /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/ /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py && sleep 0'"'"''
<9.134.124.159> (0, b'', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 -tt 9.134.124.159 '/bin/sh -c '"'"'/usr/bin/python /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py && sleep 0'"'"''
<9.134.124.159> (0, b'\r\n{"started": 1, "invocation": {"module_args": {"jid": "48991476669.20840", "mode": "status", "_async_dir": "/root/.ansible_async"}}, "finished": 0, "ansible_job_id": "48991476669.20840"}\r\n', b'Shared connection to 9.134.124.159 closed.\r\n')
ASYNC POLL on 9.134.124.159: jid=48991476669.20840 started=1 finished=0


Using module file /Users/yangyu/projects/ansible/lib/ansible/modules/async_status.py
<9.134.124.159> PUT /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmp640778bz TO /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py
<9.134.124.159> SSH: EXEC sftp -b - -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 '[9.134.124.159]'
<9.134.124.159> (0, b'sftp> put /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmp640778bz /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py\n', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'chmod u+x /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/ /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py && sleep 0'"'"''
<9.134.124.159> (0, b'', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 -tt 9.134.124.159 '/bin/sh -c '"'"'/usr/bin/python /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py && sleep 0'"'"''
<9.134.124.159> (0, b'\r\n{"started": 1, "invocation": {"module_args": {"jid": "48991476669.20840", "mode": "status", "_async_dir": "/root/.ansible_async"}}, "finished": 0, "ansible_job_id": "48991476669.20840"}\r\n', b'Shared connection to 9.134.124.159 closed.\r\n')
ASYNC POLL on 9.134.124.159: jid=48991476669.20840 started=1 finished=0


Using module file /Users/yangyu/projects/ansible/lib/ansible/modules/async_status.py
<9.134.124.159> PUT /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmp_snd1eu9 TO /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py
<9.134.124.159> SSH: EXEC sftp -b - -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 '[9.134.124.159]'
<9.134.124.159> (0, b'sftp> put /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmp_snd1eu9 /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py\n', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'chmod u+x /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/ /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py && sleep 0'"'"''
<9.134.124.159> (0, b'', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 -tt 9.134.124.159 '/bin/sh -c '"'"'/usr/bin/python /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py && sleep 0'"'"''
<9.134.124.159> (0, b'\r\n{"started": 1, "invocation": {"module_args": {"jid": "48991476669.20840", "mode": "status", "_async_dir": "/root/.ansible_async"}}, "finished": 0, "ansible_job_id": "48991476669.20840"}\r\n', b'Shared connection to 9.134.124.159 closed.\r\n')
ASYNC POLL on 9.134.124.159: jid=48991476669.20840 started=1 finished=0


Using module file /Users/yangyu/projects/ansible/lib/ansible/modules/async_status.py
<9.134.124.159> PUT /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmpk13c6mm6 TO /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py
<9.134.124.159> SSH: EXEC sftp -b - -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 '[9.134.124.159]'
<9.134.124.159> (0, b'sftp> put /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmpk13c6mm6 /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py\n', b'')


<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'chmod u+x /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/ /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py && sleep 0'"'"''
<9.134.124.159> (0, b'', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 -tt 9.134.124.159 '/bin/sh -c '"'"'/usr/bin/python /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py && sleep 0'"'"''
<9.134.124.159> (0, b'\r\n{"started": 1, "invocation": {"module_args": {"jid": "48991476669.20840", "mode": "status", "_async_dir": "/root/.ansible_async"}}, "finished": 0, "ansible_job_id": "48991476669.20840"}\r\n', b'Shared connection to 9.134.124.159 closed.\r\n')
ASYNC POLL on 9.134.124.159: jid=48991476669.20840 started=1 finished=0


Using module file /Users/yangyu/projects/ansible/lib/ansible/modules/async_status.py
<9.134.124.159> PUT /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmpfjdhm2l7 TO /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py
<9.134.124.159> SSH: EXEC sftp -b - -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 '[9.134.124.159]'
<9.134.124.159> (0, b'sftp> put /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmpfjdhm2l7 /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py\n', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'chmod u+x /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/ /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py && sleep 0'"'"''
<9.134.124.159> (0, b'', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 -tt 9.134.124.159 '/bin/sh -c '"'"'/usr/bin/python /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py && sleep 0'"'"''
<9.134.124.159> (0, b'\r\n{"started": 1, "invocation": {"module_args": {"jid": "48991476669.20840", "mode": "status", "_async_dir": "/root/.ansible_async"}}, "finished": 0, "ansible_job_id": "48991476669.20840"}\r\n', b'Shared connection to 9.134.124.159 closed.\r\n')
ASYNC POLL on 9.134.124.159: jid=48991476669.20840 started=1 finished=0


Using module file /Users/yangyu/projects/ansible/lib/ansible/modules/async_status.py
<9.134.124.159> PUT /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmpzqpsa6hl TO /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py
<9.134.124.159> SSH: EXEC sftp -b - -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 '[9.134.124.159]'
<9.134.124.159> (0, b'sftp> put /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmpzqpsa6hl /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py\n', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'chmod u+x /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/ /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py && sleep 0'"'"''
<9.134.124.159> (0, b'', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 -tt 9.134.124.159 '/bin/sh -c '"'"'/usr/bin/python /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py && sleep 0'"'"''
<9.134.124.159> (0, b'\r\n{"started": 1, "invocation": {"module_args": {"jid": "48991476669.20840", "mode": "status", "_async_dir": "/root/.ansible_async"}}, "finished": 0, "ansible_job_id": "48991476669.20840"}\r\n', b'Shared connection to 9.134.124.159 closed.\r\n')
ASYNC POLL on 9.134.124.159: jid=48991476669.20840 started=1 finished=0


Using module file /Users/yangyu/projects/ansible/lib/ansible/modules/async_status.py
<9.134.124.159> PUT /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmp_66e23_9 TO /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py
<9.134.124.159> SSH: EXEC sftp -b - -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 '[9.134.124.159]'
<9.134.124.159> (0, b'sftp> put /Users/yangyu/.ansible/tmp/ansible-local-42839upbl7lkg/tmp_66e23_9 /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py\n', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'chmod u+x /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/ /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py && sleep 0'"'"''
<9.134.124.159> (0, b'', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 -tt 9.134.124.159 '/bin/sh -c '"'"'/usr/bin/python /root/.ansible/tmp/ansible-tmp-1600333358.847193-42861-146017266031103/AnsiballZ_async_status.py && sleep 0'"'"''
<9.134.124.159> (0, b'\r\n{"started": 1, "invocation": {"module_args": {"jid": "48991476669.20840", "mode": "status", "_async_dir": "/root/.ansible_async"}}, "finished": 0, "ansible_job_id": "48991476669.20840"}\r\n', b'Shared connection to 9.134.124.159 closed.\r\n')
ASYNC POLL on 9.134.124.159: jid=48991476669.20840 started=1 finished=0


fatal: [9.134.124.159]: FAILED! => {
"changed": false,
"msg": "async task did not complete within the requested time - 10s"
}

PLAY RECAP *********************************************************************
9.134.124.159 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0


Process finished with exit code 2

主要分两个步骤,先将任务在被控端跑起来,再在主控端定时轮训任务状态。第一个步骤与前文所述内容差不多,多了一份 py 代码 async_wrapper.py。

task Sleep30s 入口:

从 debug 数据可以看出,在 playbook 中设置的 async 和 poll 是作为是否开启等待与轮训任务状态的开关。

完整流程

此部分的流程只包含 Sleep30s Task,无 Gathering facts Task。

lib/ansible/plugins/action/command.py


此处通过判断 task 的 async 的值来判断是否进行 async 操作,此处为 10,即 playbook 中定义的 async 值为 10。

lib/ansible/plugins/action/__init__.py

_execute_module 方法

  1. self._make_tmp_path() 创建临时目录

    • 获取当前用户的家目录:echo ~root && sleep 0
    • 创建临时目录:( umask 77 && mkdir -p " echo /root/.ansible/tmp "&& mkdir " echo /root/.ansible/tmp/ansible-tmp-1600668712.8156748-24387-134124052814278 " && echo ansible-tmp-1600668712.8156748-24387-134124052814278=" echo /root/.ansible/tmp/ansible-tmp-1600668712.8156748-24387-134124052814278 " ) && sleep 0
      至此,完成临时目录的创建。
  2. 拷贝 AnsiballZ_command.py/root/.ansible/tmp/ansible-tmp-1600668712.8156748-24387-134124052814278/ 目录下。其功能如前文所述 wiki: #3685

  3. 定时轮训逻辑(这是相比 #3685 多出来的流程)
    这里多出来的逻辑是拷贝了一个 async_wrapper.py 文件到目标主机

    其中 async_limit 为 task 中定义的 async 值,async_jid 为随机数。

    接着生成了一个 async_cmd,也就是通过 ssh 命令,调用目标主机上的 async_wrapper.py 文件的命令。最终生成的命令为:

    1
    'ANSIBLE_ASYNC_DIR=\'~/.ansible_async\' /usr/bin/python /root/.ansible/tmp/ansible-tmp-1600668712.8156748-24387-134124052814278/async_wrapper.py 586569230138 10 /root/.ansible/tmp/ansible-tmp-1600668712.8156748-24387-134124052814278/AnsiballZ_command.py _'

    其中 async_wrapper.py 文件的内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    _ANSIBALLZ_WRAPPER = True # For test-module.py script to tell this is a ANSIBALLZ_WRAPPER
    def _ansiballz_main():

    import os
    import os.path
    import sys
    import __main__
    scriptdir = None
    try:
    scriptdir = os.path.dirname(os.path.realpath(__main__.__file__))
    except (AttributeError, OSError):
    pass
    excludes = set(('', '.', scriptdir))
    sys.path = [p for p in sys.path if p not in excludes]
    import base64
    import runpy
    import shutil
    import tempfile
    import zipfile
    if sys.version_info < (3,):
    PY3 = False
    else:
    PY3 = True
    ZIPDATA = """omitted..."""
    def invoke_module(modlib_path, temp_path, json_params):
    z = zipfile.ZipFile(modlib_path, mode='a')
    sitecustomize = u'import sys\nsys.path.insert(0,"%s")\n' % modlib_path
    sitecustomize = sitecustomize.encode('utf-8')
    zinfo = zipfile.ZipInfo()
    zinfo.filename = 'sitecustomize.py'
    zinfo.date_time = ( 2020, 9, 21, 6, 26, 19)
    z.writestr(zinfo, sitecustomize)
    z.close()
    sys.path.insert(0, modlib_path)
    from ansible.module_utils import basic
    basic._ANSIBLE_ARGS = json_params

    runpy.run_module(mod_name='ansible.modules.async_wrapper', init_globals=None, run_name='__main__', alter_sys=True)
    print('{"msg": "New-style module did not handle its own exit", "failed": true}')
    sys.exit(1)
    def debug(command, zipped_mod, json_params):
    basedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'debug_dir')
    args_path = os.path.join(basedir, 'args')
    if command == 'explode':
    z = zipfile.ZipFile(zipped_mod)
    for filename in z.namelist():
    if filename.startswith('/'):
    raise Exception('Something wrong with this module zip file: should not contain absolute paths')
    dest_filename = os.path.join(basedir, filename)
    if dest_filename.endswith(os.path.sep) and not os.path.exists(dest_filename):
    os.makedirs(dest_filename)
    else:
    directory = os.path.dirname(dest_filename)
    if not os.path.exists(directory):
    os.makedirs(directory)
    f = open(dest_filename, 'wb')
    f.write(z.read(filename))
    f.close()
    f = open(args_path, 'wb')
    f.write(json_params)
    f.close()
    print('Module expanded into:')
    print('%s' % basedir)
    exitcode = 0
    elif command == 'execute':
    sys.path.insert(0, basedir)
    with open(args_path, 'rb') as f:
    json_params = f.read()
    from ansible.module_utils import basic
    basic._ANSIBLE_ARGS = json_params
    runpy.run_module(mod_name='ansible.modules.async_wrapper', init_globals=None, run_name='__main__', alter_sys=True)
    print('{"msg": "New-style module did not handle its own exit", "failed": true}')
    sys.exit(1)
    else:
    print('WARNING: Unknown debug command. Doing nothing.')
    exitcode = 0
    return exitcode
    ANSIBALLZ_PARAMS = '{"ANSIBLE_MODULE_ARGS": {}}'
    if PY3:
    ANSIBALLZ_PARAMS = ANSIBALLZ_PARAMS.encode('utf-8')
    try:
    temp_path = tempfile.mkdtemp(prefix='ansible_ansible.legacy.async_wrapper_payload_')
    zipped_mod = os.path.join(temp_path, 'ansible_ansible.legacy.async_wrapper_payload.zip')
    with open(zipped_mod, 'wb') as modlib:
    modlib.write(base64.b64decode(ZIPDATA))
    if len(sys.argv) == 2:
    exitcode = debug(sys.argv[1], zipped_mod, ANSIBALLZ_PARAMS)
    else:
    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
    finally:
    try:
    shutil.rmtree(temp_path)
    except (NameError, OSError):
    pass
    sys.exit(exitcode)
    if __name__ == '__main__':
    _ansiballz_main()

    与 AnsiballZ_command.py 中的逻辑类似,最终执行的代码来自 ZIP_DATA 中的数据,此处为 ansible.modules.async_wrapper 中的 main 函数,同理,此函数也可以在 ansible 的源代码中找到,其内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    ➜  tree ansible
    .
    ├── __init__.py
    ├── module_utils
    │ ├── __init__.py
    │ ├── _text.py
    │ ├── basic.py
    │ ├── common
    │ │ ├── __init__.py
    │ │ ├── _collections_compat.py
    │ │ ├── _json_compat.py
    │ │ ├── _utils.py
    │ │ ├── collections.py
    │ │ ├── file.py
    │ │ ├── parameters.py
    │ │ ├── process.py
    │ │ ├── sys_info.py
    │ │ ├── text
    │ │ │ ├── __init__.py
    │ │ │ ├── converters.py
    │ │ │ └── formatters.py
    │ │ ├── validation.py
    │ │ └── warnings.py
    │ ├── compat
    │ │ ├── __init__.py
    │ │ ├── _selectors2.py
    │ │ └── selectors.py
    │ ├── distro
    │ │ ├── __init__.py
    │ │ └── _distro.py
    │ ├── parsing
    │ │ ├── __init__.py
    │ │ └── convert_bool.py
    │ ├── pycompat24.py
    │ └── six
    │ └── __init__.py
    └── modules
    ├── __init__.py
    └── async_wrapper.py
    8 directories, 29 files

    其中 async_wrapper.py 在 ansible 中的位置为:lib/ansible/modules/async_wrapper.py

  4. 实际执行任务。这里执行的其实是 async_wrapper.py,通过 async_wrapper.py 来调用 AnsiballZ_command.py。

    此处总共分成两个步骤:

  • 加权限:chmod u+x ~/.ansible/tmp/ansible-tmp-1600668712.8156748-24387-134124052814278/ ...AnsiballZ_command.py ...async_wrapper.py && sleep 0(路径有省略)
  • 执行 async_wrapper.py:python ...async_wrapper.py 586569230138 10 ...AnsiballZ_command.py _ && sleep 0(路径有省略)

承接第3小点,此处最终执行的是 ansible/modules/async_wrapper.py 的代码逻辑

ansible/modules/async_wrapper.py

  1. 取出传入的参数,即 async、command.py 等
  2. 确定实际要执行的命令(业务命令,也就是对用户来说,相在目标机上执行的命令)
  3. 通过 os.fork() 剥离主进程与子进程,子进程执行 XXX_command.py,主进程返回结束。此处涉及大量的进程间通信(IPC),代码逻辑有点绕,但是有如下几点可以确认:
  • 父(主)进程提前返回,也就是 ssh 命令执行完毕。
  • 子进程在父进程退出后,变成孤儿进程,然后通过 daemonize_self() 函数,将自己变成一个脱离父进程的新进程
  • 新进程会再次 fork,创建一个新子进程,并在新子进程中执行 XXX_command.py ,新进程会一直等待新子进程执行完毕。

经过上述步骤,ssh 命令已返回。

轮训执行结果

执行完上述流程后,会返回一个 result,内容如下:

1
2
3
4
5
6
7
8
9
{
"started": 1,
"finished": 0,
"results_file": "/root/.ansible_async/732064027558.2514",
"ansible_job_id": "732064027558.2514",
"_ansible_parsed": true,
"changed": true,
"_ansible_no_log": false
}

可以看出,结果是存放在被控端的某个目录下,并在 lib/ansible/executor/task_executor.py 中,通过对 task 中的 async 和 poll 值的判断,来决定是否进行结果轮询:

轮训的流程与执行一条 ansible 命令类似,详见 #3685 ,此处不再赘述,不同的地方在于:

  1. 上传的文件是 AnsiballZ_async_status.py

  2. 通过 ssh 开始执行的文件是 AnsiballZ_async_status.py,最终执行的是: lib/ansible/modules/async_status.py

拿到被控机上面的 task log 后,会对 task 的执行状态进行判断:

如果在 async 秒内,没有完成 task,此 task 将直接返回超时;

不过从此处来看,当 task 执行完毕后,可能还存在主控端去被控端轮询 task 的情况。

Ansible ad-hoc 执行流程

背景

Ansible 封装了很多脚本,以 Module、Play 的形式呈现,这里以一条简单的 shell 命令作为切入点。
在开始前,将目标机的信息,先写入 cat /etc/ansible/hosts 中。

1
9.134.124.159:36000

所用到的命令如下:

1
ansible all -vvv -a "ls /root" -u root

通过打开一些 debug 日志,可以确定执行连接操作时,一定会执行 ansible/lib/ansible/plugins/connection/ssh.py 中的代码。

执行步骤

完整的日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
META: ran handlers
<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'echo ~root && sleep 0'"'"''
<9.134.124.159> (0, b'/root\n', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo /root/.ansible/tmp `"&& mkdir "` echo /root/.ansible/tmp/ansible-tmp-1599536717.558343-92837-98923700243036 `" && echo ansible-tmp-1599536717.558343-92837-98923700243036="` echo /root/.ansible/tmp/ansible-tmp-1599536717.558343-92837-98923700243036 `" ) && sleep 0'"'"''
<9.134.124.159> (0, b'ansible-tmp-1599536717.558343-92837-98923700243036=/root/.ansible/tmp/ansible-tmp-1599536717.558343-92837-98923700243036\n', b'')

<9.134.124.159> Attempting python interpreter discovery
<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'echo PLATFORM; uname; echo FOUND; command -v '"'"'"'"'"'"'"'"'/usr/bin/python'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.7'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.6'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.5'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python2.7'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python2.6'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'/usr/libexec/platform-python'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'/usr/bin/python3'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python'"'"'"'"'"'"'"'"'; echo ENDFOUND && sleep 0'"'"''
<9.134.124.159> (0, b'PLATFORM\nLinux\nFOUND\n/usr/bin/python\n/usr/bin/python3.6\n/usr/bin/python2.7\n/usr/bin/python2.6\n/usr/libexec/platform-python\n/usr/bin/python3\n/usr/bin/python\nENDFOUND\n', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'/usr/bin/python && sleep 0'"'"''
<9.134.124.159> (0, b'{"osrelease_content": "NAME=\\"Tencent tlinux\\"\\nVERSION=\\"2.2 (Final)\\"\\nID=\\"tlinux\\"\\nID_LIKE=\\"rhel fedora centos\\"\\nVERSION_ID=\\"2.2\\"\\nPRETTY_NAME=\\"Tencent tlinux 2.2 (Final)\\"\\nANSI_COLOR=\\"0;31\\"\\nCPE_NAME=\\"cpe:/o:tlinux:linux:2\\"\\nHOME_URL=\\"http://tlinux.oa.com/\\"\\nBUG_REPORT_URL=\\"http://tapd.oa.com/tlinux/bugtrace/bugreports/my_view/\\"\\n\\n", "platform_dist_result": ["centos", "7.2", "Final"]}\n', b'')

Using module file /Users/yangyu/projects/ansible/lib/ansible/modules/command.py
<9.134.124.159> PUT /Users/yangyu/.ansible/tmp/ansible-local-9283485g9ot9_/tmpkr7p4e1t TO /root/.ansible/tmp/ansible-tmp-1599536717.558343-92837-98923700243036/AnsiballZ_command.py
<9.134.124.159> SSH: EXEC sftp -b - -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 '[9.134.124.159]'
<9.134.124.159> (0, b'sftp> put /Users/yangyu/.ansible/tmp/ansible-local-9283485g9ot9_/tmpkr7p4e1t /root/.ansible/tmp/ansible-tmp-1599536717.558343-92837-98923700243036/AnsiballZ_command.py\n', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'chmod u+x /root/.ansible/tmp/ansible-tmp-1599536717.558343-92837-98923700243036/ /root/.ansible/tmp/ansible-tmp-1599536717.558343-92837-98923700243036/AnsiballZ_command.py && sleep 0'"'"''
<9.134.124.159> (0, b'', b'')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 -tt 9.134.124.159 '/bin/sh -c '"'"'/usr/bin/python /root/.ansible/tmp/ansible-tmp-1599536717.558343-92837-98923700243036/AnsiballZ_command.py && sleep 0'"'"''
<9.134.124.159> (0, b'\r\n{"changed": true, "end": "2020-09-08 11:45:53.183432", "stdout": "11.sh\\n11.zip\\nInstallHalyard.sh\\n[\\nabc.txt\\nagent.zip\\nbefore.txt\\ncloud-agent.log\\nclouddriver-prome.yaml\\nclouddriver.git?token=bd40137b365a6a789b7f5904cff87958ba5b158b\\nclouddriver.log\\ncoding-cd\\ncoding-cd-grpc.yaml\\nconfig_hub_oa_com.sh\\nconfig_vscode_server.sh\\ndemo\\ndemo.yaml\\ndev\\ndevopsAgent\\ndevopsDaemon\\nenable_internet_proxy.sh\\nfile.txt\\nfix_devcloud.logs\\ngeneric-aloe.txt\\nhi.db\\niProxy.sh\\nindex.html\\ninit_data_disk.sh\\ninit_devcloud_remote.sh\\ninstall.sh\\ninstallAgent.sh\\ninstall_ift.sh\\njre\\njre.zip\\nlog.txt\\nlogs\\npost-script.text\\nprome.yaml\\npush_master\\nrevert_image_source.sh\\nruntime\\nset_linux_welcome.sh\\nstart.sh\\nstop.sh\\nszx\\ntelegraf.conf\\nuninstall.sh\\nworker-agent.jar\\nworkspace", "cmd": ["ls", "/root"], "rc": 0, "start": "2020-09-08 11:45:53.178756", "stderr": "", "delta": "0:00:00.004676", "invocation": {"module_args": {"creates": null, "executable": null, "_uses_shell": false, "strip_empty_ends": true, "_raw_params": "ls /root", "removes": null, "argv": null, "warn": false, "chdir": null, "stdin_add_newline": true, "stdin": null}}}\r\n', b'Shared connection to 9.134.124.159 closed.\r\n')

<9.134.124.159> ESTABLISH SSH CONNECTION FOR USER: root
<9.134.124.159> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'rm -f -r /root/.ansible/tmp/ansible-tmp-1599536717.558343-92837-98923700243036/ > /dev/null 2>&1 && sleep 0'"'"''
<9.134.124.159> (0, b'', b'')
9.134.124.159 | CHANGED | rc=0 >>
11.sh
11.zip
InstallHalyard.sh
[
abc.txt
...
META: ran handlers
META: ran handlers

Process finished with exit code 0

对上述日志进行简化,可知其大致有 7 个步骤,分别如下:

简化后,其所执行的命令如下:

1
2
3
4
5
6
7
8
9
ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'echo ~root && sleep 0'"'"''
ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo /root/.ansible/tmp `"&& mkdir "` echo /root/.ansible/tmp/ansible-tmp-1599546740.791937-1678-254955456024140 `" && echo ansible-tmp-1599546740.791937-1678-254955456024140="` echo /root/.ansible/tmp/ansible-tmp-1599546740.791937-1678-254955456024140 `" ) && sleep 0'"'"''
ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'echo PLATFORM; uname; echo FOUND; command -v '"'"'"'"'"'"'"'"'/usr/bin/python'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.7'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.6'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.5'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python2.7'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python2.6'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'/usr/libexec/platform-python'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'/usr/bin/python3'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python'"'"'"'"'"'"'"'"'; echo ENDFOUND && sleep 0'"'"''
ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'/usr/bin/python && sleep 0'"'"''
sftp -b - -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 '[9.134.124.159]'
ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'chmod u+x /root/.ansible/tmp/ansible-tmp-1599546740.791937-1678-254955456024140/ /root/.ansible/tmp/ansible-tmp-1599546740.791937-1678-254955456024140/AnsiballZ_command.py && sleep 0'"'"''
ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 -tt 9.134.124.159 '/bin/sh -c '"'"'/usr/bin/python /root/.ansible/tmp/ansible-tmp-1599546740.791937-1678-254955456024140/AnsiballZ_command.py && sleep 0'"'"''
ssh -C -o ControlMaster=auto -o ControlPersist=60s -o Port=36000 -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="root"' -o ConnectTimeout=10 -o ControlPath=/Users/yangyu/.ansible/cp/4846fe66a5 9.134.124.159 '/bin/sh -c '"'"'rm -f -r /root/.ansible/tmp/ansible-tmp-1599546740.791937-1678-254955456024140/ > /dev/null 2>&1 && sleep 0'"'"''

上传到被控端的文件

上传到被控制的文件,在 ~/.ansible/tmp/xxx 下,是一个 Python 脚本,名称为:AnsiballZ_command.py,里面还包含一段压缩文件的 base64。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#!/usr/bin/python
# -*- coding: utf-8 -*-
_ANSIBALLZ_WRAPPER = True # For test-module.py script to tell this is a ANSIBALLZ_WRAPPER
def _ansiballz_main():

import os
import os.path
import sys
import __main__
scriptdir = None
try:
scriptdir = os.path.dirname(os.path.realpath(__main__.__file__))
except (AttributeError, OSError):
pass
excludes = set(('', '.', scriptdir))
sys.path = [p for p in sys.path if p not in excludes]
import base64
import runpy
import shutil
import tempfile
import zipfile
if sys.version_info < (3,):
PY3 = False
else:
PY3 = True
# ZIPDATA 的值已经省略
ZIPDATA = """xxxxxxxxxxx"""
def invoke_module(modlib_path, temp_path, json_params):
z = zipfile.ZipFile(modlib_path, mode='a')
sitecustomize = u'import sys\nsys.path.insert(0,"%s")\n' % modlib_path
sitecustomize = sitecustomize.encode('utf-8')
zinfo = zipfile.ZipInfo()
zinfo.filename = 'sitecustomize.py'
zinfo.date_time = ( 2020, 9, 7, 5, 54, 28)
z.writestr(zinfo, sitecustomize)
z.close()
sys.path.insert(0, modlib_path)
from ansible.module_utils import basic
basic._ANSIBLE_ARGS = json_params

runpy.run_module(mod_name='ansible.modules.command', init_globals=None, run_name='__main__', alter_sys=True)
print('{"msg": "New-style module did not handle its own exit", "failed": true}')
sys.exit(1)
def debug(command, zipped_mod, json_params):
basedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'debug_dir')
args_path = os.path.join(basedir, 'args')
if command == 'explode':
z = zipfile.ZipFile(zipped_mod)
for filename in z.namelist():
if filename.startswith('/'):
raise Exception('Something wrong with this module zip file: should not contain absolute paths')
dest_filename = os.path.join(basedir, filename)
if dest_filename.endswith(os.path.sep) and not os.path.exists(dest_filename):
os.makedirs(dest_filename)
else:
directory = os.path.dirname(dest_filename)
if not os.path.exists(directory):
os.makedirs(directory)
f = open(dest_filename, 'wb')
f.write(z.read(filename))
f.close()
f = open(args_path, 'wb')
f.write(json_params)
f.close()
print('Module expanded into:')
print('%s' % basedir)
exitcode = 0
elif command == 'execute':
sys.path.insert(0, basedir)
with open(args_path, 'rb') as f:
json_params = f.read()
from ansible.module_utils import basic
basic._ANSIBLE_ARGS = json_params
runpy.run_module(mod_name='ansible.modules.command', init_globals=None, run_name='__main__', alter_sys=True)
print('{"msg": "New-style module did not handle its own exit", "failed": true}')
sys.exit(1)
else:
print('WARNING: Unknown debug command. Doing nothing.')
exitcode = 0
return exitcode
ANSIBALLZ_PARAMS = '{"ANSIBLE_MODULE_ARGS": {"_raw_params": "ls /root", "_ansible_check_mode": false, "_ansible_no_log": false, "_ansible_debug": false, "_ansible_diff": false, "_ansible_verbosity": 3, "_ansible_version": "2.11.0.dev0", "_ansible_module_name": "ansible.legacy.command", "_ansible_syslog_facility": "LOG_USER", "_ansible_selinux_special_fs": ["fuse", "nfs", "vboxsf", "ramfs", "9p", "vfat"], "_ansible_string_conversion_action": "warn", "_ansible_socket": null, "_ansible_shell_executable": "/bin/sh", "_ansible_keep_remote_files": false, "_ansible_tmpdir": "/root/.ansible/tmp/ansible-tmp-1599457948.85933-15430-201759115318770/", "_ansible_remote_tmp": "~/.ansible/tmp"}}'
if PY3:
ANSIBALLZ_PARAMS = ANSIBALLZ_PARAMS.encode('utf-8')
try:
temp_path = tempfile.mkdtemp(prefix='ansible_ansible.legacy.command_payload_')
zipped_mod = os.path.join(temp_path, 'ansible_ansible.legacy.command_payload.zip')
with open(zipped_mod, 'wb') as modlib:
modlib.write(base64.b64decode(ZIPDATA))
if len(sys.argv) == 2:
exitcode = debug(sys.argv[1], zipped_mod, ANSIBALLZ_PARAMS)
else:
invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
finally:
try:
shutil.rmtree(temp_path)
except (NameError, OSError):
pass
sys.exit(exitcode)
if __name__ == '__main__':
_ansiballz_main()

AnsiballZ_command.py 执行时,会首先将 ZIPDATA 还原成一个压缩文件,然后在压缩文件中加入一个 sitecustomize.py。准备完成后,将以 runpy.run_module() 的方式,执行压缩文件中的 Python 代码。可以看出,我们所需要执行的命令,储存在 ANSIBALLZ_PARAMS 变量中,并赋值给了 basic._ANSIBLE_ARGS

ZIPDATA 压缩包

首先,压缩文件中的内容如下。此压缩包通过手段获取到后,解压后,名称为 ansible,里面的文件如 basic.py、command.py 来自所执行命令 ansible 所在的包下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
╰─$ tree ansible
ansible
├── __init__.py
├── module_utils
│ ├── __init__.py
│ ├── _text.py
│ ├── basic.py
│ ├── common
│ │ ├── __init__.py
│ │ ├── _collections_compat.py
│ │ ├── _json_compat.py
│ │ ├── _utils.py
│ │ ├── collections.py
│ │ ├── file.py
│ │ ├── parameters.py
│ │ ├── process.py
│ │ ├── sys_info.py
│ │ ├── text
│ │ │ ├── __init__.py
│ │ │ ├── converters.py
│ │ │ └── formatters.py
│ │ ├── validation.py
│ │ └── warnings.py
│ ├── compat
│ │ ├── __init__.py
│ │ ├── _selectors2.py
│ │ └── selectors.py
│ ├── distro
│ │ ├── __init__.py
│ │ └── _distro.py
│ ├── parsing
│ │ ├── __init__.py
│ │ └── convert_bool.py
│ ├── pycompat24.py
│ └── six
│ └── __init__.py
└── modules
├── __init__.py
└── command.py

其次,压缩文件中代码的执行。从 runpy.run_module 中的 mod_name='ansible.modules.command' 猜测,是要执行压缩包下的 modules/command.py。此文件的执行,包括了一个 AnsibleModule 的初始化,在它的构造函数中,可以看出来,获取了 basic._ANSIBLE_ARGS 的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class AnsibleModule(object):
def __init__(self, argument_spec, bypass_checks=False, no_log=False,
mutually_exclusive=None, required_together=None,
required_one_of=None, add_file_common_args=False,
supports_check_mode=False, required_if=None, required_by=None):
...

self._load_params()
...

##############################basic.py##############################
def _load_params(self):
self.params = _load_params()
##############################basic.py##############################
def _load_params():
global _ANSIBLE_ARGS
if _ANSIBLE_ARGS is not None:
buffer = _ANSIBLE_ARGS
....

最终,从 command.py 执行到 basic.py,以开启执行系统命令,作为我们所要脚本(即:ls /root)执行的开始

1
cmd = subprocess.Popen(args, **kwargs)

等待 shell 命令执行完毕

在此处,为 cmd 的 stdout、stderr 注册了可读事件到 selector,并在一个死循环中轮训 selector,如果有 cmd.stdout、cmd.stderr 可读事件,就将对应的数据,追加到相应的变量上。当 cmd 执行完成后,退出死循环,并返回 cmd 执行后的 stdout、stderr。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
selector.register(cmd.stdout, selectors.EVENT_READ)
selector.register(cmd.stderr, selectors.EVENT_READ)
...
while True:
events = selector.select(1)
for key, event in events:
b_chunk = key.fileobj.read()
if b_chunk == b(''):
selector.unregister(key.fileobj)
if key.fileobj == cmd.stdout:
stdout += b_chunk
elif key.fileobj == cmd.stderr:
stderr += b_chunk
...
# only break out if no pipes are left to read or
# the pipes are completely read and
# the process is terminated
if (not events or not selector.get_map()) and cmd.poll() is not None:
break
# No pipes are left to read but process is not yet terminated
# Only then it is safe to wait for the process to be finished
# NOTE: Actually cmd.poll() is always None here if no selectors are left
elif not selector.get_map() and cmd.poll() is None:
cmd.wait()
# The process is terminated. Since no pipes to read from are
# left, there is no need to call select() again.
break
...
return (rc, stdout, stderr)

控制中心

是什么地方定义上上面这 8 个步骤?产生上面 8 个步骤的地方,开始于:ansible/plugins/action/command.py,其中 self._execute_module() 完成了前面的 7 个操作,self._remove_tmp_path() 完成了删除 ~/.ansible/tmp/xxxxx 的任务。

重试机制

  1. 什么时候进行重试
  • an exception is caught
  • ssh returns 255(ControlPersist 超时不能用或者远程主机连不上)
  1. 什么时候不进行重试。
  • sshpass returns 5 (invalid password, to prevent account lockouts)
  • remaining_tries is < 2
  • retries limit reached

重试的方式,是通过一个对连接函数的闭包操作完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def _ssh_retry(func):
@wraps(func)
def wrapped(self, *args, **kwargs):
remaining_tries = int(C.ANSIBLE_SSH_RETRIES) + 1
cmd_summary = u"%s..." % to_text(args[0])
conn_password = self.get_option('password') or self._play_context.password
for attempt in range(remaining_tries):
...
return wrapped
################################################################
@_ssh_retry
def _run(self, cmd, in_data, sudoable=True, checkrc=True):
"""Wrapper around _bare_run that retries the connection
"""
return self._bare_run(cmd, in_data, sudoable=sudoable, checkrc=checkrc)

可借鉴点

2 | Golang: 笔记

Go语言允许用户定义类型。当用户声明一个新类型时,这个声明就给编译器提供了一个框架,告知必要的内存大小和表示信息。声明后的类型与内置类型的运作方式类似。Go语言里声明用户定义的类型有两种方法。最常用的方法是使用关键字struct,它可以让用户创建一个结构类型。零值:当声明变量时,这个变量对应的值总是
阅读更多

1 | Golang:基础数据结构

三个常用的数据类型的大致实现,有点绕,但是很有收获!切片 Slice数据结构在64位架构的机器上,一个切片需要24字节的内存:指针字段需要8字节,长度和容量字段分别需要8字节。var slice []int 创建的数据结构如下:使用对新切片的长度与容量的计算规则如下:对底层数组容量是k的切片slic
阅读更多

Dockerfiles 官网 doc 笔记

Docker 通过 Dockerfile 中的指令来构建镜像。
Docker 镜像由很多镜像构成,每一层对应一个 Dockerfile 里面的指令。
一个运行中的容器,由镜像的所有层加上可写层构成,所有的读写都在最上面的可写层。
容器应该是无状态的,销毁、重建应该花费最小的配置。

(build context)构建上下文

1
docker build -f ~/Dockerfile.hi context-dir
  • 通过 -f 指定 Dockerfile 的路径

  • context-dir 即为构建上下文,该文件夹下面所有的内容都会被传递给 Docker daemon,用来构建镜像。构建上下文包含不相关的内容,会影响构建速度、镜像大小。

可通过编写 .dockerignore 文件,并放置在构建上下文的目录下,达到类似于 .gitignore 的效果,将不必要的文件(夹)不发送给 Docker Daemon。

有如下几种从 stdin 构建镜像的方式:

- 表是占位,从 stdin 读取

  1. 无构建上下文(不需要拷贝文件到镜像中)

模板

1
docker build [OPTIONS] -

示例

1
echo -e 'FROM busybox\nRUN echo "hello world"' | docker build -

1
2
3
4
docker build -<<EOF
FROM busybox
RUN echo "hello world"
EOF

上述两种方式,都不会给 Docker Daemon 发送构建上下文,但是注意不要在 Dockerfile 中使用 COPYADD ,这样会导致构建失败。

  1. 传递本地构建上下文

模板

1
docker build [OPTIONS] -f- PATH

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
# create a directory to work in
mkdir example
cd example

# create an example file
touch somefile.txt

# build an image using the current directory as context, and a Dockerfile passed through stdin
docker build -t myimage:latest -f- . <<EOF
FROM busybox
COPY somefile.txt .
RUN cat /somefile.txt
EOF
  1. 传递远程构建上下文

模板

1
docker build [OPTIONS] -f- PATH

示例

1
2
3
4
docker build -t myimage:latest -f- https://github.com/docker-library/hello-world.git <<EOF
FROM busybox
COPY hello.c .
EOF

多阶段构建

使 Dockerfile 在容易维护、阅读的基础上,减少镜像的大小

基础用法

在一个 Dockerfile 中使用多个 FROM,每个 FROM 构建一个镜像,在镜像之间的拷贝操作变得容易。如下:

1
2
3
4
5
6
7
8
9
10
11
FROM golang:1.7.3
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]

--from=0 ,按照数组下标从 0 开始,第一个 FROM 构建的镜像为0,依次类推。当然也可以给 FROM 构建的镜像命名:

1
2
3
4
5
6
7
8
9
10
11
FROM golang:1.7.3 AS builder
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]

其实 --from=xxx 中的 xxx 也可以为其它的、不在此 Dockerfile 中产生的镜像

其它用法

  1. 只构建指定的构建阶段,即某特定 FROM 所代表的镜像。沿用前面的例子,可以只构建 builder 阶段的镜像。
1
docker build --target builder -t alexellis2/href-counter:latest .
  1. 后续的 FROM 可以以前面的 FROM 构建的镜像做为 base。
1
2
3
4
5
6
7
8
9
10
FROM alpine:latest as builder
RUN apk --no-cache add build-base

FROM builder as build1
COPY source1.cpp source.cpp
RUN g++ -o /binary source.cpp

FROM builder as build2
COPY source2.cpp source.cpp
RUN g++ -o /binary source.cpp

构建缓存

显式声明不使用缓存: docker build --no-cache=true ... 。不做显式声明,默认可能会利用构建缓存,能成功利用构建缓存的情况:

  1. 如果从缓存中存在的一个镜像开始构建,那么会将基于此父镜像的所有子镜像的 Dockerfile 拉出来做一个对比,看下一条指令是否相同,如果不相同,缓存失效。
  2. 对比 Dockerfile 内容是否相同。一些特殊命令,需要更多检测。
  3. ADDCOPY ,它们操作的文件对象都会被作为 checksum 的一部分,不一致则缓存失效。
  4. 除了 ADDCOPY ,其他命令带来的文件改变,不会被计入 checksum,也就是在匹配镜像时,会忽略此部分的文件的不同。

其他建议写法

  1. 不安装多余的包。降低复杂度、依赖性、镜像大小和构建时间。

  2. 解耦应用,一个容器只关心一件事情,以达到水平扩展和容器的复用。但是并意味着一个容器一个进程一成不变。

  3. 减少镜像的层数。只有 RUN , COPY , ADD 三个指令添加层数,其他的指令只会创建临时的层,并不会增加镜像大小。所以,分多个阶段来构建镜像,只拷贝需要的文件到镜像中,能有效减少镜像的大小。

  4. 为多行参数排序。按字母顺序排序。避免包重复。如下:

1
2
3
4
5
6
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion

Dockerfile 指令

FROM

三种方式

1
2
3
FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]

几个要点

  • FROM 开启一个构建
  • Dockerfile 的第一个必须是 FROM ,但是 FROM 前面可以有 ARG 来声明变量
  • Dockerfile 中可以出现多个 FROM 来实现多阶段构建
  • 可以为构建阶段添加别名,在后续 FROMCOPY --from=<name|index> 中使用

ARG 的使用样例

1
2
3
4
5
6
ARG  CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD /code/run-app

FROM extras:${CODE_VERSION}
CMD /code/run-extras

LABEL

注意事项

  • 字符串中含有空格,必须转义或使用 ""
  • 字符串中含有 " ,必须转义
  • 用自己的反转域名作为 label 前缀,必须对域名有权限
  • Docker 保留的前缀: com.docker.* , io.docker.* , org.dockerproject.*
  • key 应该使用小写字母数字、 .-

-

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Set one or more individual labels
LABEL com.example.version="0.0.1-beta"
LABEL vendor1="ACME Incorporated"
LABEL vendor2=ZENITH\ Incorporated
LABEL com.example.release-date="2015-02-12"
LABEL com.example.version.is-production=""

# Set multiple labels on one line
LABEL com.example.version="0.0.1-beta" com.example.release-date="2015-02-12"

# Set multiple labels at once, using line-continuation characters to break long lines
LABEL vendor=ACME\ Incorporated \
com.example.is-beta= \
com.example.is-production="" \
com.example.version="0.0.1-beta" \
com.example.release-date="2015-02-12"

查看 label

1
docker image inspect

RUN

两种形式

1
2
3
4
5
# shell form
RUN <command>

# exec form
RUN ["executable", "param1", "param2"]

shell form 将会在 shell 中运行,Linux 默认为 /bin/sh -C ,Windows 默认为 cmd /S /C ,默认 shell 可以通过 SHELL 指令进行指定。

exec form 会被解析成 JSON 数组,因此需要用 " 扩起来。

注: RUN [ "echo", "$HOME" ] 无法读取变量,要用 RUN [ "sh", "-c", "echo $HOME" ] 才行。

在镜像顶层之上,创建新的镜像层,然后运行命令,结束后,持久化为一个只读镜像层。

apt-get

使用 RUN 指令,最多的就是执行 apt-get 之类的代码,来安装依赖。有如下注意点:

  • 避免运行 apt-get upgradedist-upgrade
  • 使用 apt-get install -y foo 来自动升级一个依赖包
  • RUN apt-get updateapt-get install 包含在同一个 RUN 指令中

一个比较推荐的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
RUN apt-get update && apt-get install -y \
aufs-tools \
automake \
build-essential \
curl \
dpkg-sig \
libcap-dev \
libsqlite3-dev \
mercurial \
reprepro \
ruby1.9.1 \
ruby1.9.1-dev \
s3cmd=1.1.* \
&& rm -rf /var/lib/apt/lists/*

Debian 和 Ubuntu 会自动运行 apt-get clean

管道符

1
RUN wget -O - https://some.site | wc -l > /number

默认只以最后一个命令 wc 的状态作为整个 RUN 指令的成功或失败,即使 wget 失败了。但可以通过设置 set -o pipefail 避免失败被忽略。如下:

1
RUN set -o pipefail && wget -O - https://some.site | wc -l > /number

CMD

CMD 指令和我印象中的不太一样。

三种形式

  • CMD [“executable”,”param1”,”param2”] (exec form, 首推)
  • CMD [“param1”,”param2”] (作为 ENTRYPOINT 的默认参数, 不推荐使用,除非很了解 ENTRYPOINT)
  • CMD command param1 param2 (shell form)

只能有一个 CMD 指令,多个 CMD 指令的情况,最后一个 CMD 指令生效。目的是提供可执行命令或参数(此时必须指定 ENTRYPOINT)。
exec form 不提供变量替换。
shell form 默认使用 /bin/sh -c 执行;当执行命令不需要 shell 时,可以使用 exec shell。

在构建期间:RUN 会执行,并将结果作为镜像层进行提交;CMD 则不会执行。

CMD 的使用示例(以 nginx 为例):

  • 错误❌ : CMD service nginx start
  • 正确✅ : CMD ["nginx", "-g", "daemon off;"]
    对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。 CMD service nginx start 会被理解为 CMD [ "sh", "-c", "service nginx start"] ,因此主进程实际上是 sh。那么当 service nginx start 命令结束后,sh 也就结束了,sh 作为主进程退出了,自然就会令容器退出。

EXPOSE

这个指令印象中是暴露一个容器的端口,但仔细想了想这个指令,对它的作用产生了怀疑。如果这个指令能暴露端口的话,那么和 docker run -p 是什么关系。

EXPOSE 声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。这个指令的好处为:

  1. 方便镜像使用者理解以配置端口映射
  2. 运行时,使用 docker run -P 为被 EXPOSE 声明过的容器的端口,随机映射一个宿主机端口。

ENV

单纯设置环境变量

格式有两种:

1
2
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...

在 ENV 后面的指令/运行时,能访问该环境变量。

COPY

从宿主机,拷贝文件到镜像中

如果源路径为文件夹,复制的时候不是直接复制该文件夹,而是将文件夹中的内容复制到目标路径。

权限问题:默认情况下,新拷贝的文件的 UID 和 GID 都是 0,即归属 root 用户。可以通过 --chown USER:GROUP 来修改文件所属(仅适合用于创建 Linux 容器)。

从 STDIN 构建的话,没有构建上下文,不能使用 COPY。

COPY 还可以接受 --from=<name|index> 从镜像中拷贝文件。

ADD

之前没见过

与 COPY 类似,但是会自动将压缩包解压到目标文件夹。如下: ADD rootfs.tar.xz /

一般情况用 COPY,需要自动解压压缩包的情景再用 ADD。

ENTRYPOINT

这部分内容之前并没仔细了解过,觉得就是简单的容器的启动命令,填上就行,但是看完这部分的文档后,刷新了认知。

两种启动模式:

  • exec form (preferred form): ENTRYPOINT ["executable", "param1", "param2"]
  • shell form: ENTRYPOINT command param1 param2

PID 1 进程

顾名思义,进程号为 1 的进程。特殊的地方在于,PID 1 进程可以接收发给容器的 UNIX 信号

有什么用?

PID 1 进程的用处,可以通过下面的 Dockerfile 来展示:

1
2
FROM ubuntu
ENTRYPOINT top -b

构建完成后,执行下面的命令,可以看到关闭容器花费了非常长的时间:

非 PID 1

当执行 docker stop 时,容器并未完全退出, docker stop 会在超时后,发送 SIGKILL 关闭容器中的其他进程。

如果 top -b 是 PID 1 进程呢?修改一下 Dockerfile 如下:

1
2
FROM ubuntu
ENTRYPOINT exec top -b

可以很明显看出,很顺畅地就完成了 docker stop

PID 1

exec form

ENTRYPOINT 中的命令默认 PID 为 1,即不会通过某种 shell 来启动(也就无法进行变量替换)。
如果是以一个脚本来启动服务,需要在脚本中使用 exec 或 gosu 来执行。

  1. 什么是 gosu?
  1. 什么是 exec?

shell form

  • CMD、 docker run 命令行参数都会被忽略掉。
  • ENTRYPOINT 会以 /bin/sh -c 的子进程运行,即非 PID 1 进程,无法接收 UNIX 信号。
  • 可以通过 exec 达到让 ENTRYPOINT 指定的服务 PID 为 1。

ENTRYPOINT & CMD

两者之间的规定:

  1. Dockerfile 中至少有一个 ENTRYPOINT 或 CMD。也就说可以有多个,但是最后一个才会生效
  2. ENTRYPOINT should be defined when using the container as an executable.Why?
  3. CMD 可以用作 ENTRYPOINT 的默认参数 或 for executing an ad-hoc command in a container.
  4. CMD will be overridden when running the container with alternative arguments.What?

两者之间的协同关系:

No ENTRYPOINT ENTRYPOINT exec_entry p1_entry (shell form) ENTRYPOINT [“exec_entry”, “p1_entry”](exec form)
No CMD error, not allowed /bin/sh -c exec_entry p1_entry exec_entry p1_entry
CMD [“exec_cmd”, “p1_cmd”] exec_cmd p1_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry exec_cmd p1_cmd
CMD [“p1_cmd”, “p2_cmd”] p1_cmd p2_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry p1_cmd p2_cmd
CMD exec_cmd p1_cmd /bin/sh -c exec_cmd p1_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd

VOLUME

含义:直接设置镜像的一个挂载点
用途:存放镜像创建的文件、配置、数据库的数据文件。易变、镜像中的用户数据。
方式:

1
VOLUME ["/data"]

1
2
VOLUME /var/log
VOLUME /var/log /var/db

USER

含义:指定镜像运行时的用户和用户组(不指定默认为 root)。USER 指令之后的 CMDRUNENTRYPOINT 都将以 USER 指定的用户和用户组运行。
方式:

1
2
USER <user>[:<group>]
USER <UID>[:<GID>]

避免使用 sudo,它会有一定的问题。确有需要考虑使用 gosu

WORKDIR

含义:指定工作目录,建议使用绝对路径(使用相对路径时,会以当前目录中的文件夹为工作目录)。可以根据需要多次指定,避免使用 cd。此指令对后续的 Dockerfile 指令生效,如:RUN, CMD, ENTRYPOINT, COPY, ADD
方式:

1
WORKDIR /path/to/workdir

ONBUILD

含义:构建 BaseImage 时,加入 ONBUILD 指令,会在使用(FROM BaseImage)的构建中,执行 ONBUILD 指令后,才执行此构建的构建指令。
方式:

1
2
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src

使用场景:使用 maven 编译 jar 包,并构建我们自己的业务镜像。所以我们的 Dockerfile 可以如下编写:

1
2
FROM maven:3.3-jdk-8-onbuild
CMD ["java","-jar","/usr/src/app/target/demo-1.0-SNAPSHOT-jar-with-dependencies.jar"]

为啥可以这样?这是因为在 maven:3.3-jdk-8-onbuild 这个镜像中,有 ONBUILD 指令,如下:

1
2
3
4
5
6
7
8
FROM maven:3-jdk-8

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

ONBUILD ADD . /usr/src/app

ONBUILD RUN mvn install

Example Link

工作流程:

  1. 遇到 ONBUILD 指令,添加触发器到镜像的 metadata 中,所以 ONBUILD 不影响当前构建的镜像。
  2. 在构建完成后,触发器会被添加到镜像的 manifest 中,可以通过 docker inspect 进行查看。
  3. 当被用作基础镜像(FROM xxx)后,新构建执行触发器,即 ONBUILD 指令,全部成功执行完后,才开始新镜像的构建。
  4. 触发器在执行后会被清除。

HEALTHCHECK

方式:

1
2
3
4
# check container health by running a command inside the container
HEALTHCHECK [OPTIONS] CMD command
# disable any healthcheck inherited from the base image
HEALTHCHECK NONE

其中 OPTIONS 可以做如下设置:

  • --interval=DURATION (default: 30s)
  • --timeout=DURATION (default: 30s)
  • --start-period=DURATION (default: 0s)
  • --retries=N (default: 3)

docker 容器的健康状况:

  • starting:初始状态。
  • healthy:当有任何一次健康检查通过时
  • unhealthy:当连续 retries 次健康检查都失败时
  • failed:单次检查时间超过 timeout

如何写一条 HEALTHCHECK 指令?
本质上是执行一条 shell 命令,不同的返回值,表示不同的含义。

  • 0:成功
  • 1:失败
  • 2:预留,勿用

示例:

1
2
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1

针对此容器的健康检查,只检查 80 端口的服务,是否能正常返回。

Reference:

  1. https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
  2. https://docs.docker.com/engine/reference/builder/

Golang面试题库

Goalng

  1. context作用,原理,超时控制
    golang context的理解,context主要用于父子任务之间的同步取消信号,本质上是一种协程调度的方式。另外在使用context时有两点值得注意:上游任务仅仅使用context通知下游任务不再需要,但不会直接干涉和中断下游任务的执行,由下游任务自行决定后续的处理操作,也就是说context的取消操作是无侵入的;context是线程安全的,因为context本身是不可变的(immutable),因此可以放心地在多个协程中传递使用。

  2. 切片和数组区别
    基础问题。

  3. channel关闭阻塞问题,goroutine如何调度,gopark是怎么回事?PMG模型描述,谁创建的PMG,runtime是怎么个东西,怎么启动第一个goroutine
    golang CPS并发模型和PMG模型的理解。

  4. go逃逸分析怎么回事,内存什么时候栈分配什么时候堆分配
    内存方面问题,这个网上很多,自己理解完整正确。

  5. sync.Map实现原理,适用的场景
    go 1.9 官方提供sync.Map 来优化线程安全的并发读写的map。该实现也是基于内置map关键字来实现的。
    这个实现类似于一个线程安全的 map[interface{}]interface{} . 这个map的优化主要适用了以下场景:
    (1)给定key的键值对只写了一次,但是读了很多次,比如在只增长的缓存中;
    (2)当多个goroutine读取、写入和覆盖的key值不相交时。
    更进一步,可看sync.Map源码。

  6. go语言有什么优点和缺点
    优势:容易学习,生产力,并发,动态语法。劣势:包管理,错误处理,缺乏框架。

  7. Go框架用过哪些,有看源码吗
    优势:beego,go-micro,gin等

  8. Go GC算法,三色标记法描述
    自己找,网上有

  9. Go内存模型(tcmalloc)
    tcmalloc是线程缓存的malloc,实现了高效的多线程内存管理,用于替代系统的内存分配相关的函数

算法

  1. 行列都是有序的二维数组,查找k是否存在,时间复杂度

    1
    2
    3
    4
    5
    1 3 5 7 9

    3 5 7 9 11

    4 6 8 10 12

    二分查找:O(log2(max(m,n)))

  2. 有序数组,有2N+1个数,其中N个数成对出现,仅1个数单独出现,找出那个单独出现的数.,时间复杂度
    1,1,2,2,3,4,4,5,5,6,6,7,7
    答案为3

  3. O(log2(2N))二分查找,查找中间位置的数相等值是在左边还是右边?左边则再左子数组继续查找,右边则在右子数组继续查找。

  4. 100亿个数求top100,时间复杂度
    分组查找或bitmap

  5. 100亿个数和100亿个数求交集,时间复杂度
    全排列问题,自己找去

  6. hash算法实现(类似crc32或者murmur),保证随机性和均匀性,减少哈希冲突
    考的是hash算法的了解,需要知道一些经典哈希算法实现。

  7. 100个球,一次只能拿2-5个,你先拿,我后拿,怎么保证你能拿到最后一个球
    一次2-5,去掉先手,最后回合剩余7个即可保证拿到最后一个球。因此,先手拿2个,每一回合保证拿掉球的总数为7,即可。(100-2)/7=14回合。

  8. 正整数数组,求和为sum的组合 换零钱,1,5,10元都很充足,给你N元去换零钱,多少种换法
    算法题给定一个有n个正整数的数组A和一个整数sum,求选择数组A中部分数字和为sum的方案数,动态规划法。

  9. 图的最短路径

操作系统

  1. Select/epoll,IO多路复用,底层数据结构,epoll的几个函数,两种模式
    Select/epoll 问题,网上很多

  2. 抢占式调度是什么回事
    进程优先级和时间分片等方面理解

  3. 用户态和内核态
    系统态(内核态),操作系统在系统态运行——运行操作系统程序
    用户态(也称为目态),应用程序只能在用户态运行——运行用户程序

MySQL

  1. innodb和myisam区别(事务,索引,锁。。。)

  2. B+树和B树区别,优缺点
    B树每个节点都存储key和data,所有节点组成这棵树,并且叶子节点指针为null。只有叶子节点存储data,叶子节点包含了这棵树的所有键值,叶子节点不存储指针,顺序访问指针,也就是每个叶子节点增加一个指向相邻叶子节点的指针。

  3. B树和二叉查找树或者红黑色区别

  4. 聚簇索引什么特点,为什么这样,顺序查询的实现,回表查询,联合索引特性
    聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据
    非聚簇索引:将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行,myisam通过key_buffer把索引先缓存到内存中,当需要访问数据时(通过索引访问数据),在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这也就是为什么索引不在key buffer命中时,速度慢的原因

  5. 大表分页查询,10亿行数据,查找第N页数据,怎么优化
    根据查询的页数和查询的记录数可以算出查询的id的范围,可以使用 id between and 来查询。

  6. 悲观锁和乐观锁,mysql相关锁说一下
    说下概念,其他网上找:
    乐观锁( Optimistic Locking):对加锁持有一种乐观的态度,即先进行业务操作,不到最后一步不进行加锁,”乐观”的认为加锁一定会成功的,在最后一步更新数据的时候再进行加锁。
    悲观锁(Pessimistic Lock):悲观锁对数据加锁持有一种悲观的态度。因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

  7. 如何分库分表
    1)垂直分表
    也就是“大表拆小表”,基于列字段进行的。一般是表中的字段较多,将不常用的, 数据较大,长度较长(比如text类型字段)的拆分到“扩展表“。一般是针对那种几百列的大表,也避免查询时,数据量太大造成的“跨页”问题。
    2)垂直分库
    垂直分库针对的是一个系统中的不同业务进行拆分,比如用户User一个库,商品Producet一个库,订单Order一个库。切分后,要放在多个服务器上,提高性能。
    3)水平分库分表
    将单张表的数据切分到多个服务器上去,每个服务器具有相应的库与表,只是表中数据集合不同。水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等的瓶颈。

Redis

  1. 几种数据结构(list,set,zset,geohash,bitmap)实现原理

  2. pipline用来干嘛
    pipeline的作用是将一批命令进行打包,然后发送给服务器,服务器执行完按顺序打包返回。

  3. 事务
    redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。 

  4. 备份(aof/rdb)原理,哪些参数可调
    RDB是根据指定的规则定时将内存中的数据备份到硬盘上,AOF是在每次执行命令后命令本身记录下来,所以RDB的备份文件是一个二进制文件,而AOF的备份文件是一个文本文件。至于调参,网上可找。

  5. 网络模型
    redis网络模型,网上找,需要理解。

  6. 为什么单线程就能hold住几万qps
    I/O复用,Reactor 设计模式

  7. 热点key怎么处理

  • 热key加载到系统内存中,直接从系统内存中取,而不走到redis层。
  • redis集群,热点备份分布到集群中,避免单台redis集中访问。
  1. 一致性hash解决什么问题
    redis集群和负载均衡

  2. redis集群(主从,高可用,扩展节点)

Kafka

  1. 消息是否按照时间有序,kafka分区的数据是否有序,如何保证有序
    不保证按时间有序,主题在单个分区是有序的。

  2. 如何保证有序?
    kafka topic 只设置一个分区,或者producer将消息发送到指定分区

  3. Kafka为什么吞吐量高
    1)顺序读写
    kafka的消息是不断追加到文件中的,这个特性使kafka可以充分利用磁盘的顺序读写性能,顺序读写不需要硬盘磁头的寻道时间,只需很少的扇区旋转时间,所以速度远快于随机读写。
    2)零拷贝
    利用Linux kernel”零拷贝(zero-copy)”系统调用机制,就是跳过“用户缓冲区”的拷贝,建立一个磁盘空间和内存的直接映射,数据不再复制到“用户态缓冲区”。
    3)分区
    kafka中的topic中的内容可以被分为多分区存在,每个分区又分为多个段,所以每次操作都是针对一小部分做操作,很轻便,并且增加并行操作的能力。
    4)批量发送
    kafka允许进行批量发送消息,producter发送消息的时候,可以将消息缓存在本地,等到了固定条件发送到kafka等消息条数到固定条数,一段时间发送一次。
    5)数据压缩
    Kafka还支持对消息集合进行压缩,Producer可以通过GZIP或Snappy格式对消息集合进行压缩。压缩的好处就是减少传输的数据量,减轻对网络传输的压力

  4. kafka的存储模型

  5. Kafka消费者多个group消费同一个topic,会重复消费吗?

项目问题

  1. 遇到过内存溢出吗?怎么解决
    主要了解有没有处理过内存泄漏导致的问题,C/C++定位内存泄漏问题;Golang和JAVA主要与GC的工作机制有关,堆内存一直增长,导致应用内存溢出等。

  2. 布隆过滤器怎么设置m,n,k的值,怎么合理安排key(用户和item越来越多,怎么保证内存不会爆)
    m,n,k 网上有实践经验,可参考。item越来越多的话,进行item的拆分,拆分本质是不要将 Hash(Key) 之后的请求分散在多个节点的多个小 bitmap 上,而是应该拆分成多个小 bitmap 之后,对一个 Key 的所有哈希函数都落在这一个小 bitmap 上。

  3. 服务雪崩怎么处理,怎么解决保证不影响线上
    限流,降级,熔断方面措施,结合后端系统架构阐述,如网关的限流和快速失败。

  4. redis和mysql数据一致性怎么保证
    重点考虑业务逻辑上写和数据的流程(异常和错误处理等),结合MQ做异步重试处理。

  5. 分布式锁应用场景,哪些坑
    锁过期了,业务还没执行完;分布式锁,redis主从同步的坑;获取到锁后,线程异常。

折腾自建博客系列

今年年初由 wordpress 迁移到 halo,主要是觉得懂点 Java,有什么定制化的需求,自己改代码会方便一些。但是也没有什么特别的需求需要定制,反而被这种东西折腾得忘记了写博客的初心。技术博客就应该简简单单,只写技术,博客怎么好看、怎么炫酷,都不重要。配置说明&前置条件在 Halo
阅读更多

0 | Golang:基础构建工具使用

Golang 里面有一堆看起来很高大上的名词,以及一些自带的工具链。

1
brew install golang

安装完之后,有一堆默认的配置信息,可以通过 go env 来进行查看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
╰─$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/akina/Library/Caches/go-build"
GOENV="/Users/akina/Library/Application Support/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/akina/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/Cellar/go/1.14.6/libexec"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/Cellar/go/1.14.6/libexec/pkg/tool/darwin_amd64"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/km/t6kk56y13ns2yf91lwq58gbh0000gn/T/go-build924722399=/tmp/go-build -gno-record-gcc-switches -fno-common"

其中有几个比较重要的变量,需要注意。

GOROOT

Golang 的安装目录。本机上的 GOROOT 为:/usr/local/Cellar/go/1.14.6/libexec,它的目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
╰─$ tree -L 1 /usr/local/Cellar/go/1.14.6/libexec
/usr/local/Cellar/go/1.14.6/libexec
├── CONTRIBUTING.md
├── CONTRIBUTORS
├── PATENTS
├── SECURITY.md
├── VERSION
├── api
├── bin
├── doc
├── favicon.ico
├── lib
├── misc
├── pkg
├── robots.txt
├── src
└── test

GOPATH

同样是一个变量,且变量值的内容是一个目录,那 Golang 拿着这个变量来做什么呢?它的作用用文字说明可能比较苍白无力、甚至有点抽象,用亲手实践来看看它到底有什么用处。在本机中,GOPATH 的值(默认值:$HOME/go)为:/Users/akina/go

1
2
3
4
5
╰─$ tree -L 1 /Users/akina/go
/Users/akina/go
├── bin
├── pkg
└── src

其中会自动生成三个文件夹,它们的作用分别为:

文件夹 作用
bin golang 编译可执行文件存放路径,可自动生成。
pkg golang编译的.a中间文件存放路径,可自动生成。
src 源码路径。按照golang默认约定,go run,go install等命令的当前工作路径(即在此路径下执行上述命令)

暂时将其视为一个普通的目录,拥有三个普通的文件夹。先看后面的 go buildgo installgo run

GOBIN

go install 编译存放路径。为空时则遵循“约定优于配置”原则,可执行文件放在各自 GOPATH 目录的 bin 文件夹中,即 $GOPATH/bin

有两种情况下,bin 目录会变得没有意义。

  • 当设置了有效的 GOBIN 环境变量以后,bin 目录就变得没有意义。
  • 如果 GOPATH 里面包含多个工作区路径的时候,必须设置 GOBIN 环境变量,否则就无法安装 Go 程序的可执行文件。

GOPROXY

如果遇到下载不下来包的情况,可以考虑尝试设置 GOPROXY 。如下(>=1.13):

1
2
3
4
5
6
7
8
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.io,direct

# 设置不走 proxy 的私有仓库,多个用逗号相隔(可选)
go env -w GOPRIVATE=*.corp.example.com

# 设置不走 proxy 的私有组织(可选)
go env -w GOPRIVATE=example.com/org_name

go build

usage: go build [-o output] [-i] [build flags] [packages]

Build compiles the packages named by the import paths, along with their dependencies, but it does not install the results.

If the arguments to build are a list of .go files from a single directory, build treats them as a list of source files specifying a single package.

When compiling packages, build ignores files that end in ‘_test.go’.

When compiling a single main package, build writes the resulting executable to an output file named after the first source file (‘go build ed.go rx.go’ writes ‘ed’ or ‘ed.exe’) or the source code directory (‘go build unix/sam’ writes ‘sam’ or ‘sam.exe’).

The ‘.exe’ suffix is added when writing a Windows executable.

When compiling multiple packages or a single non-main package, build compiles the packages but discards the resulting object, serving only as a check that the packages can be built.

  • go build 命令默认编译当前目录下的所有 go 文件
  • go build a.go 只编译 a.go 文件
    缺省编译 & 编译某个文件
    如果将编译对象换成不含有 main 函数的代码,没有任何输出
    编译 non-main 代码
  • go build IMPORT-PATH 编译在 $GOPATH/src 下面的 IMPORT-PATH 包,并在当前目录 (pwd)下,生成可执行文件(对含有 main 函数的代码来说)
    编译 GOPATH 下的包

go install

它在 go build 的基础上,将编译后的可执行文件中间文件,移动到 $GOPATH 的 bin 或 pkg 目录下。

install

go run

类似于 go build,可以在其它目录,仅指定在 $GOPATH 下的包名,即可运行该包内容;也可以指定一个含有 main 函数的文件。如下:

run

go get

用于从远程代码仓库(比如 Github 等 )上下载并安装代码包。下载源码包的go工具会自动根据不同的域名调用不同的源码工具,对应关系如下:

仓库 源码工具
BitBucket Mercurial Git
GitHub Git
Google Code Project Hosting Git, Mercurial, Subversion
Launchpad Bazaar

它会把当前的代码包下载到 $GOPATH 中的第一个工作区的 src 目录中,并安装。

  • -d:只下载不安装
  • -u:更新已下载的代码包

执行 go get 后所安装的内容

GoLand 中打开的 terminal 会自动将 $GOPATH/bin 添加到 PATH 变量中,导致在 GoLand 的 terminal 上可以使用通过 go get 安装的命令,在 iTerm2 上面就不行。只要在 .bashrc/.zshrc 中,将 $GOPATH/bin 添加到 PATH 变量中就可以在 iTerm2 中使用。

go fmt

将代码整理成 Golang 风格。

usage: go fmt [-n] [-x] [packages]

Fmt runs the command ‘gofmt -l -w’ on the packages named by the import paths. It prints the names of the files that are modified.

For more about gofmt, see ‘go doc cmd/gofmt’.
For more about specifying packages, see ‘go help packages’.

The -n flag prints commands that would be executed.
The -x flag prints commands as they are executed.

The -mod flag’s value sets which module download mode to use: readonly or vendor. See ‘go help modules’ for more.

To run gofmt with specific options, run gofmt itself.

go fmt 实际调用的是 gofmt -l -w,而 gofmt 的使用如下:

1
2
3
4
5
6
7
8
9
10
usage: gofmt [flags] [path ...]
-cpuprofile string
write cpu profile to this file
-d display diffs instead of rewriting files
-e report all errors (not just the first 10 on different lines)
-l list files whose formatting differs from gofmt's
-r string
rewrite rule (e.g., 'a[b:len(a)] -> a[b:]')
-s simplify code
-w write result to (source) file instead of stdout

gofmt 与 go 在同一级目录,都在 $GOROOT/bin 下:

看看效果:

初始样式

Before go fmt

执行 go fmt,可以明显看出,代码变整齐了。

go test

Reference

1.2 | Kubernetes:容器技术

此文是一篇大杂烩,记录一些对我很有用处的新知识。主要包括容器相关概念、实现原理、相关标准等。最大的感触就是 Golang 才是云时代的语言,很多容器相关软件的开发语言是用的 Golang。Let's go!什么是容器容器就是将软件打包成标准化单元,以用于开发、交付和部署。简单说就是:容器可以被当做一
阅读更多