Status | Authors | Coach | DRIs | Owning Stage | Created |
---|---|---|---|---|---|
proposed | devops verify | - |
Runner 통합
비 목표
이 제안은 Step Runner 바이너리를 대상 환경으로 배포하거나, 아래에 설명된 Step Runner gRPC 서비스를 시작하는 것과 같은 것은 다루지 않습니다. 이 제안의 나머지 부분은 Step Runner 바이너리가 대상 환경에 존재하고 gRPC 서비스가 실행되고 로컬 소켓에서 수신되어 있다고 가정합니다. 비슷하게, 이 제안은 Step Runner
서비스의 수명주기를 다루지 않으며, 서비스가 종료되는 경우와 업그레이드와 같은 사항을 어떻게 다룰지에 대해서도 다루지 않습니다.
관련된 청사진은 배포 및 수명주기 관리 를 참조하세요.
단계 서비스 gRPC 정의
Step Runner 서비스 gRPC 정의는 다음과 같습니다:
service StepRunner {
rpc Run(RunRequest) returns (RunResponse);
rpc FollowSteps(FollowStepsRequest) returns (stream FollowStepsResponse);
rpc FollowLogs(FollowLogsRequest) returns (stream FollowLogsResponse);
rpc Finish(FinishRequest) returns (FinishResponse);
rpc Status(StatusRequest) returns (StatusResponse);
}
message Variable {
string key = 1;
string value = 2;
bool file = 3;
bool masked = 4;
}
message Job {
repeated Variable variables = 1;
string job_id = 2;
string pipeline_id = 3;
string build_dir = 4;
repeated string token_prefixes = 5;
}
message Masking {
repeated string phrases = 1;
repeated string token_prefixes = 2;
}
message RunRequest {
string id = 1;
string work_dir = 2;
map<string,string> env = 3;
Masking masking = 4;
Job job = 5;
string steps = 6;
}
message RunResponse {
}
message FollowStepsRequest {
string id = 1;
}
message FollowStepsResponse {
StepResult result = 1;
}
message FollowLogsRequest {
string id = 1;
int32 offset = 2;
}
message FollowLogsResponse {
bytes data = 1;
}
message FinishRequest {
string id = 1;
}
message FinishResponse {
}
message Status {
string id = 1;
bool finished = 2;
int32 exit_code = 3;
google.protobuf.Timestamp start_time = 4;
google.protobuf.Timestamp end_time = 5;
}
message StatusRequest {
string id = 1;
}
message StatusResponse {
repeated Status jobs = 1;
}
Runner
는 위의 gRPC 서비스를 통해 Step Runner
와 상호작용합니다. 이 서비스는 실행 환경의 로컬 소켓에서 시작됩니다. Runner
는 우선 실행기별 프로토콜을 통해 대상 환경에 연결한 다음, 제공된 proxy
명령을 사용하여 gRPC
서비스에 연결하고, Runner
에서 Step Runner
로 gRPC
요청을 터널링합니다(자세한 내용은 Proxy Command 참조). 이는 Nesting이 전용 Mac 인스턴스에서 gRPC 서비스를 제공하는 방식과 동일합니다. 이 서비스에는 Run
, FollowSteps
, FollowLogs
, Finish
, Status
라는 다섯 가지 RPC가 있습니다.
Run
은 단계의 초기 전달입니다. FollowSteps
는 단계 결과 트레이스의 스트리밍 응답을 요청합니다. FollowLogs
는 단계 실행 중에 실행되는 프로세스에 의해 작성된 출력(stdout
/stderr
)과 Step Runner
자체가 생성한 로그의 스트리밍 응답을 동일하게 요청합니다. Finish
는 요청의 실행을 중단하고 가능한 한 빨리 리소스를 정리합니다. Status
는 지정된 작업의 상태를 나열하며, 지정된 작업이 없는 경우 Step Runner
서비스 내의 모든 활성 작업(완료된 작업을 포함하지만 Finish
되지는 않음)을 나열합니다. 예를 들어, Status
는 크래시 후 Runner
가 복구하는 데 사용될 수 있습니다.
Step Runner
gRPC 서비스는 여러 Run
페이로드를 동시에 실행할 수 있습니다. 즉, 각 Run
호출은 그에 해당하는 새로운 고루틴을 시작하고 완료될 때까지 단계를 실행합니다. 동시에 여러 Run
호출을 할 수 있습니다.
단계가 실행되는 동안 단계 결과 트레이스와 하위 프로세스 로그를 GitLab Runner로 스트리밍할 수 있습니다. 이를 통해 Runner(또는 다른 호출자)는 단계 결과 트레이스(FollowSteps
) 수준에서 실행을 따르는 것과 하위 프로세스 및 Step Runner
로그(FollowLogs
)를 작성한 방식으로 실행을 따를 수 있습니다. 로그는 특정 형식으로 작성되며, 민감한 토큰은 스트리밍되기 전에 Step Runner
에서 마스킹됩니다.
Status
를 제외한 모든 API는 동일함을 보장하는 멱등성을 가집니다. 즉, 동일한 매개변수로 동일한 API에 대해 여러 호출을 한 경우 동일한 결과를 반환해야 합니다. 예를 들어, 동일한 id
로 여러 차례 Run
을 호출할 경우 첫 번째 호출만 작업 요청의 처리가 시작되어야 하며, 후속 호출은 성공 상태를 반환하지만 그 외에는 아무것도 수행해서는 안 됩니다. 마찬가지로, 동일한 id
로 여러 차례 Finish
를 호출하면 첫 번째 호출에서 관련 작업을 종료하고 제거하고, 후속 호출에서는 아무것도 수행해서는 안 됩니다.
서비스는 클라이언트가 잘 행동할 것이라고 가정해서는 안 되며, Follow
API 중 하나에서 절대 호출하지 않거나 일찍 연결을 끊는 클라이언트뿐만 아니라, 대응하는 Run
요청에 대해 절대 Finish
를 호출하지 않는 클라이언트도 처리할 수 있어야 합니다. 이를 위해 Step Runner
프로세스는 일정 간격으로 작동하지 않거나 오랫동안 작동된 작업을 식별하고 정리합니다. 작동하지 않는 작업은 몇 시간 전에 완료되었거나(Finish
되지는 않았음)로 지정된 작업일 수 있습니다. 오랫동안 작동된 작업은 해당 기간동안 실행된 일련의 작업(아마도 출력을 생성하지 않은)일 수 있습니다.
마지막으로, 아래 실행기 실행기에 단계를 통합하는 데 도움이 되도록, 단계가 Run
/Follow*
/Finish
API를 실행하는 클라이언트 라이브러리를 제공하고 이벤트 중에 Follow*
호출의 연결을 처리하도록 권장합니다.
RunRequest 매개변수
단계들은 RunRequest.Steps
필드에 step.go 의 JSON 직렬화 버전으로 Step Runner
로 전달됩니다. Runner 자체에서는 단계 정의를 처리할 필요가 없습니다. id
필드는 Step Runner
서비스에서 실행 중인 각 요청을 고유하게 식별합니다. RunRequest.Env
필드는 각 단계가 실행될 때 환경 변수를 보관합니다.
옵션인 Job
매개변수는 해당 CI 작업에서 선택된 매개변수를 포함할 것입니다. Job
에는 해당 CI 작업의 빌드 디렉터리가 포함될 것이며, Job.BuildDir
은 RunRequest.WorkDir
로 복사되어야 하며, 요청의 모든 단계를 유지하기 위해 그 디렉터리에서 호출되어야 합니다. RunRequest
는 또한 CI 작업의 환경 변수(즉, CI 구성의 작업 및 전역 수준에서 정의된 variables
)를 포함할 것입니다. Runner에서 RunRequest
를 수행할 때, 변수는 Job.Variables
에 포함되어야 하며, RunRequest.Env
는 비워두어야 합니다. 실행 요청이 처리될 때, 파일 타입의 변수는 파일에 작성될 것이며, 변수는 확장되어 RunRequest.Env
에 복사되며, 나머지 요청에서 Job
필드는 버릴 것입니다. 변수는 Step Runner 서비스에서 환경 변수나 경로와 같은 실행 환경의 객체를 참조할 수 있기 때문에, 수행시 변수가 확장되어야 하며, 예를 들어 파일 유형의 변수는 전통적인 Runner 작업 실행과 같은 경로에 쓰여야 합니다. 비슷하게, Job.Variables
에서 마스킹되어야 하는 구절들은 추출되어 Masking.Phrases
로 사용되며, Job.TokenPrefixes
은 Masking.TokenPrefixes
로 복사되어야 합니다.
단계를 실행하려는 Runner 이외의 클라이언트는 Job
필드를 생략할 수 있으며, 이 경우 Masking
과 Env
필드는 호출자에 의해 직접 채워져야 합니다.
로그 형식
FollowLogs
API에서 발행된 로그 라인은 다음 형식을 가져야합니다.
<timestamp> <stream> <stdout/stderr> <append flag> <message>
이것은 동일한 로그 형식이 이 Merge Request에서 실행으로 도입되었습니다.
이 형식으로 서식이 지정된 원시 로그 메시지가 포맷되기 전에 민감한 변수나 토큰을 마스킹하는 책임은 Step Runner
에 있을 것입니다.
변수 마스킹에 사용되는 라이브러리는 GitLab Runner
및 Step Runner
사이에서 공유되어야 합니다. (관련 모듈들)를 확인하세요.
프록시 명령
Step Runner
바이너리에는 (일반적으로 텍스트 기반) stdin
/stdout
/stderr
기반 프로토콜에서 데이터를 프록시하는 명령이 포함될 것입니다.
이 명령은 gRPC 서비스와 동일한 호스트에서 실행되며, stdin
에서 입력을 읽어 이를 로컬 소켓을 통해 gRPC 서비스로 전달하고, 동일한 소켓을 통해 gRPC 서비스로부터의 출력을 받아들이며, 이를 stdout
/stderr
를 통해 클라이언트에게 전달합니다.
이 명령은 클라이언트(예: Runner)가 stdin
/stderr
/stdout
기반 프로토콜을 통해 gRPC
서비스로 투명하게 터널링할 수 있도록 하여 Docker 이미지에서 Step Runner 서비스의 gRPC 포트를 노출할 필요를 제거하고 VM에서 SSH 포트 포워딩을 설정하는 번거로움을 없애주며, Runner가 SSH 및 docker exec
와 같은 이미 구축된 프로토콜을 사용하여 Step Runner
와 상호작용할 수 있도록 할 것입니다. stdout
는 Step Runner
서비스에서의 응답 작성에 예약되어야 하며, stderr
는 proxy
명령 자체에서 발생하는 오류를 위해 예약되어야 합니다.
실행자
다음은 GitLab Runner가 각 Runner 실행자에서 Step Runner에 연결하는 방법입니다.
인스턴스
인스턴스 실행자는 현재와 동일하게 SSH를 통해 액세스됩니다. 그러나 명령을 파이핑하는 대신 프록시 명령을 호출하고, 이 명령은 다시 알려진 위치에 있는 Step Runner 소켓에 연결됩니다. 그런 다음 Runner는 직접 gRPC
호출을 수행하고 SSH
연결을 통해 gRPC
서비스로 투명하게 터널링할 수 있습니다. 이는 전용 Mac 인스턴스에서 Runner가 VM을 만들기 위해 Nesting 서버를 호출하는 방식과 동일합니다.
이는 Step Runner가 작업 실행 환경에 존재하고 시작되어 있어야 하는 것을 요구합니다.
도커
Step Runner가 존재하고 gRPC 서비스가 실행 중인 요구는 도커 실행자(및 docker-autoscaler
)에 대해서도 마찬가지입니다. 그러나 컨테이너 내부의 gRPC 서비스에 연결하기 위해 Runner는 컨테이너로 docker exec
를 수행하고 컨테이너 내부에서 gRPC 서비스로 연결하기 위해 프록시 명령을 실행할 것입니다. 클라이언트는 그런 다음 docker exec
의 stdin
에 쓸 수 있으며, 이는 투명하게 gRPC 서비스로 프록시될 것이며, 그의 stdout/stderr
에서는 gRPC 서비스로부터의 응답이 포함될 것입니다.
쿠버네티스
쿠버네티스 노드의 Kubelet은 실행중인 Pod의 컨테이너에서 프로세스를 시작할 exec API를 노출합니다. 이를 사용하여 호출자가 Pod 내부에서 gRPC
호출을 할 수 있는 브릿지 프로세스를 exec create
하여 연결할 것입니다. 이는 도커 실행자와 유사하게 Runner가 gRPC
서비스에 대한 연결을 수행할 수 있도록 합니다.
이 보호된 Kubelet API에 액세스하기 위해서는 Pod에 대한 쿠버네티스 API를 사용해야 합니다. 호출자는 /exec
로 끝나는 pod의 URL에 POST할 수 있으며, 그 후에 이를 양방향 바이트 스트리밍을 위한 SPDY 프로토콜로 협상할 수 있습니다. 따라서 GitLab Runner는 쿠버네티스 API를 사용하여 Step Runner 서비스에 연결하고 작업 페이로드를 전달할 수 있습니다.
이것은 kubectl exec
가 작동하는 방식과 동일합니다. 사실, SPDY 협상과 같은 대부분의 내부 기능은 client-go
라이브러리로 제공됩니다. 따라서 Runner는 Kubectl로 쉘에 직접 셸을 띄우는 대신에 필요한 라이브러리를 가져와서 쿠버네티스 API에 직접 호출할 수 있습니다.
역사적으로 쿠버네티스 실행자의 약점 중 하나는 단일 exec를 통해 전체 작업을 실행하는 것이었습니다. 이를 완화하기 위해 Runner는 기존의 셸 프로세스에 “다시 연결”할 수 있는 attach 명령을 사용합니다.
그러나 이는 Step Runner에 대해서는 필요하지 않습니다. 왜냐하면 exec는 장시간 실행되는 gRPC 프로세스로의 브릿지를 구축하기 때문입니다. 연결이 끊기면 Runner는 또 다른 연결을 exec하여 계속해서 follow
와 같은 RPC 호출을 수행할 것입니다.