Skip to content

Commit c5645a6

Browse files
committed
add kjh_contribution file
1 parent acef159 commit c5645a6

1 file changed

Lines changed: 257 additions & 0 deletions

File tree

week6/juhyeong_contribution.md

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
# Zuul Pipeline의 실행 흐름과 Build Node lifecycle
2+
3+
## 1. 주제 선정 이유
4+
5+
Zuul은 speculative gating 기능을 갖춘 이벤트 기반 CI/CD 시스템이다.
6+
그러나 내부 실행 모델은 실제 런타임 관점에서 바라보지 않으면 추상적으로
7+
느껴질 수 있다.
8+
9+
노드가 어떻게 요청되고, 프로비저닝되며, 사용되고, 정리·삭제되는지를
10+
추적하면서 Zuul 프로세스의 흐름을 이해하고자 한다.
11+
12+
## 2. 시스템 구조: node는 어디에 위치하는가
13+
14+
Zuul의 주요 구성 요소는 다음과 같다:
15+
16+
- Scheduler (Control Plane)
17+
- Merger (Speculative Git 상태 생성)
18+
- Executor (job 실행)
19+
- Launcher (node Provisioning 담당)
20+
- ZooKeeper (분산 이벤트 큐 및 상태 머신 저장, 조정)
21+
- SQL Database (빌드 이력 저장)
22+
- 외부 코드 리뷰 시스템 (예: Gerrit)
23+
24+
Zuul 프로세스의 개념적 흐름은 다음과 같다:
25+
26+
Event
27+
→ Scheduler
28+
→ job Graph
29+
→ node Request
30+
→ node Provision
31+
→ Executor 연결
32+
→ job 실행
33+
→ Cleanup
34+
→ node 삭제
35+
36+
## 3. node lifecycle의 상태 전이 모델
37+
38+
Zuul에서의 node는 일회성 연산 단위로 Ephemeral node 모델을 따른다.
39+
40+
이로 인해 다음과 같은 효과를 얻을 수 있다:
41+
42+
- Secret 잔존 방지
43+
- 상태 오염 방지
44+
- 재현성 보장
45+
46+
node lifecycle은 다음과 같은 상태 모델로 이해할 수 있다:
47+
```
48+
state:
49+
INIT
50+
→ BUILDING
51+
→ READY
52+
→ IN_USE
53+
→ USED / HOLD
54+
→ DELETING
55+
→ DELETED
56+
→ FAILED
57+
→ DELETING
58+
→ DELETED
59+
```
60+
각 상태 전이는 이벤트 기반이며, ZooKeeper를 통해 조정된다.
61+
62+
## 4. nodeset - job이 요구하는 실행 환경 정의
63+
64+
Zuul에서 job은 직접 node를 요청하지 않고 nodeset을 정의한다.
65+
nodeset은 job의 실행 환경을 추상화한 객체로, job이 ansible을 실행하기 위해 필요로 하는 노드들의 집합을 의미한다.
66+
67+
nodeset은 다음과 같은 정보를 포함한다:
68+
69+
- 노드의 이름
70+
- 노드에 필요한 label
71+
- 노드의 역할 (예: controller, worker 등)
72+
73+
예시:
74+
```
75+
nodeset:
76+
nodes:
77+
- name: controller
78+
label: ubuntu-22.04
79+
- name: worker
80+
label: ubuntu-22.04
81+
```
82+
이 경우와 같이 job은 단일 노드가 아니라 두 개 이상의 노드를 동시에
83+
요구하기도 한다.
84+
85+
## 5. node Request 생성 - Scheduler
86+
87+
Scheduler는 job을 실행하기 위해 nodeset을 기반으로 nodeRequest를
88+
생성한다.
89+
90+
node Request는 하나의 객체로, nodeset이라는 추상화된 실행 환경을 실제
91+
리소스로 변환하는 요청이다.
92+
93+
node Request는 다음과 같은 상태 모델을 가진다:
94+
```
95+
state:
96+
REQUESTED
97+
→ PENDING
98+
→ FULFILLED
99+
→ FAILED
100+
```
101+
102+
request 객체를 생성하여 Zookeeper에 전송하는 Scheduler 코드 일부:
103+
```
104+
#zuul/nodepool.py
105+
def requestnodes(self, build_set, job, relative_priority):
106+
# Create a copy of the nodeset to represent the actual nodes
107+
# returned by nodepool.
108+
nodeset = job.nodeset.copy()
109+
req = model.nodeRequest(self.sched.hostname, build_set, job,
110+
nodeset,relative_priority)
111+
self.requests[req.uid] = req
112+
113+
if nodeset.nodes:
114+
self.sched.zk.submitnodeRequest(req, self._updatenodeRequest)
115+
# Logged after submission so that we have the request id
116+
self.log.info("Submitted node request %s" % (req,))
117+
self.emitStats(req)
118+
else:
119+
self.log.info("Fulfilling empty node request %s" % (req,))
120+
req.state = model.STATE_FULFILLED
121+
self.sched.onnodesProvisioned(req)
122+
del self.requests[req.uid]
123+
return req
124+
```
125+
1. Scheduler가 프로젝트 설정 평가
126+
2. job 실행 순서에 대한 DAG(Directed Acyclic Graph) 생성
127+
3. DAG의 각 job에 대해 node Request 생성
128+
129+
- 이 시점에 아직 실제 node는 존재하지 않음
130+
131+
## 6. 리소스 할당 (Provisioning) - Launcher
132+
133+
Launcher 컴포넌트는 node Request를 감시하여 리소스를 할당한다:
134+
135+
1. Zookeeper에 저장된 node request 감시 :
136+
```
137+
requests = sorted(self.zk.nodeRequestIterator(), key=_sort_key)
138+
139+
if req.state != zk.REQUESTED:
140+
continue
141+
```
142+
2. zk lock 획득: 분산 시스템 안정성 유지
143+
```
144+
self.zk.locknodeRequest(req, blocking=False)
145+
```
146+
3. ProviderManager를 통한 프로비저닝
147+
```
148+
rh = pm.getRequestHandler(self, req)
149+
rh.run()
150+
```
151+
이 과정에서 실제 node가 생성된다.
152+
생성된 node의 상태와 정보는 ZooKeeper를 통해 추적되고 제공된다.
153+
154+
4. 각 객체의 상태 변환
155+
```
156+
nodeRequest: REQUESTED → PENDING → FULFILLED / FAILED
157+
node: INIT → BUILDING → READY
158+
```
159+
160+
## 7. Executor 연결
161+
162+
node가 READY 상태가 되면:
163+
164+
1. Executor가 ZooKeeper가 제공하는 node에 대한 정보를 기반으로 ansible
165+
inventory 생성.
166+
167+
2. ssh-agent 실행, 이후 해당 agent를 이용하여 ansible이 node와 연결
168+
```
169+
#zuul/executor/server.py
170+
class SshAgent(object):
171+
def __init__(self, zuul_event_id=None, build=None):
172+
self.env = {}
173+
self.ssh_agent = None
174+
self.log = get_annotated_logger(
175+
logging.getLogger(\"zuul.ExecutorServer\"),
176+
zuul_event_id, build=build)
177+
```
178+
3. Secret 주입
179+
180+
4. Ansible Playbook 실행
181+
```
182+
#zuul/executor/server.py
183+
cmd = [self.executor_server.ansible_manager.getAnsibleCommand(
184+
ansible_version), verbose, playbook.path\]
185+
186+
result, code = self.runAnsible(
187+
cmd, timeout, playbook, ansible_version)
188+
```
189+
## 8. Speculative Execution과 node 증폭 효과
190+
191+
변경사항 큐가 다음과 같다고 가정하자:
192+
193+
A → B → C
194+
195+
Zuul은 변경사항을 테스트하기 위해 merge된 것으로 가정한 다음 상태를
196+
병렬로 평가한다:
197+
198+
State1 = base + A
199+
State2 = base + A + B
200+
State3 = base + A + B + C
201+
202+
각 speculative state는 독립적인 job DAG를 실행한다.
203+
204+
이때 A가 실패할 경우:
205+
206+
1. B와 C의 speculative state 무효화.
207+
2. 실행 중이던 job 취소.
208+
3. 관련 node는 Cleanup 단계로 진입.
209+
4. Scheduler는 Queue 재구성 (B → C)
210+
5. B, B+C를 위한 새로운 node Request.
211+
212+
이로 인해 병렬로 한번에 진행되는 평가가 많아질수록 node 수요가 증가하고
213+
상위 변경 빌드의 실패가 많아질수록 node 수요가 급격하게 증가한다.
214+
215+
## 9. Pipeline Window - speculative 폭 제어 메커니즘
216+
217+
Zuul에서는 Pipeline Window라는 병렬 테스트에 사용되는 리소스의 양을
218+
제어하는 장치를 제공한다.
219+
220+
Pipeline Window는 한번에 진행되는 Speculative Execution의 수를 제한하는
221+
방법으로 리소스 수요를 제어한다.
222+
예를 들어 window크기를 3으로 제한할 경우,
223+
224+
225+
큐가 A → B → C → D → E 일 때
226+
227+
State1 = base + A
228+
State2 = base + A + B
229+
State3 = base + A + B + C
230+
State4 = base + A + B + C + D
231+
State5 = base + A + B + C + D + E
232+
233+
가 아닌 :
234+
235+
State1 = base + A
236+
State2 = base + A + B
237+
State3 = base + A + B + C
238+
239+
를 우선 실행하고 각 작업이 완료될 때마다 뒤에 남은 D, E를 앞으로
240+
가져와서 추가적으로 실행하게 된다. 따라서 실패로 인해 새로운 리소스를
241+
요청할 경우 낭비되는 리소스가 줄어드는 효과를 볼 수 있다.
242+
243+
## 10. Cleanup
244+
245+
job 완료 후:
246+
247+
1. Artifact 업로드
248+
2. Log 스트리밍
249+
250+
3. Cleanup 루틴 실행
251+
```
252+
pm = self._nodepool.getProviderManager(node.provider)
253+
pm.startnodeCleanup(node)
254+
```
255+
4. node: Used → DELETING 상태 전이
256+
5. 리소스 제거
257+

0 commit comments

Comments
 (0)