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);
}
...
Runner는 위의 gRPC 서비스를 통해 Step Runner와 상호 작용하며, 실행 환경의 로컬 소켓에서 시작됩니다. Runner는 먼저 대상 환경에 해당하는 프로토콜을 통해 연결한 후, 제공된 proxy
명령을 사용하여 gRPC
서비스에 연결하고 (seeProxy Command) Runner에서 Step Runner로 gRPC
요청을 투명하게 터널링합니다. Nesting도 동일한 방식으로 전용 Mac 인스턴스에서 gRPC 서비스를 제공합니다. 이 서비스에는 Run
, FollowSteps
, FollowLogs
, Finish
및 Status
의 5개의 RPC가 있습니다.
Run
은 단계들의 초기 전달을 의미합니다. FollowSteps
는 단계 결과 트레이스를 스트리밍 응답으로 요청합니다. FollowLogs
는 프로세스 실행 중에 생성된 출력(stdout
/stderr
) 및 Step Runner 자체에서 생성된 로그를 스트리밍 응답으로 요청합니다. Finish
는 요청 실행을 중지하고 가능한 즉시 리소스를 정리합니다. Status
는 지정된 작업의 상태를 나열하거나 지정된 작업이 없는 경우 Step Runner 서비스의 모든 활성 작업(완료된 작업도 포함되지만 Finish
되지 않은 작업)을 나열합니다. 예를 들어 Runner는 충돌 이후 회복하기 위해 Status
를 사용할 수 있습니다.
Step Runner gRPC 서비스는 한 번에 여러 Run
페이로드를 실행할 수 있습니다. 즉, 각각의 Run
호출은 새로운 고루틴을 시작하고 완료될 때까지 단계를 실행합니다. 동시에 여러 Run
호출이 발생할 수 있습니다.
단계가 실행되는 동안, 단계 결과 트레이스 및 하위 프로세스 로그가 GitLab Runner로 스트리밍될 수 있습니다. 이를 통해 Runner(또는 다른 호출자)는 단계 결과 트레이스(FollowSteps
) 수준 및 하위 프로세스 및 Step Runner 로그(FollowLogs
)로 작성된 실행을 추적할 수 있습니다. 로그는 특정 형식으로 작성되며, 민감한 토큰은 마스킹된 후 Runner로 스트리밍됩니다.
Status
를 제외한 모든 API는 중복 호출이 가능합니다. 즉, 동일한 매개변수로 동일한 API에 대한 여러 호출은 동일한 결과를 반환해야 합니다. 예를 들어, Run
이 동일한 id
로 여러 번 호출된다면 첫 번째 호출만 작업 요청을 처리해야 하며, 후속 호출은 성공 상태를 반환하지만 그 외에 아무 것도 수행하면 안 됩니다. 마찬가지로, 동일한 id
로 여러 번의 Finish
호출은 첫 번째 호출로 해당 작업을 완료하고 제거해야 하며, 후속 호출로 아무 작업도 수행하면 안 됩니다.
서비스는 클라이언트가 잘 행동하지 않을 것으로 가정하고, Follow
API 중 하나에서 호출을 절대로 하지 않거나 조기에 연결을 해제하는 클라이언트, 그리고 해당 Run
요청에 대해 절대로 Finish
를 호출하지 않는 클라이언트를 다룰 수 있어야 합니다. 이를 위해 Step Runner
프로세스는 정기적으로 스캔을 수행하여 오래된 또는 비정상적인 작업을 식별하고 정리할 수 있어야 합니다. 지난 시간 동안(그리고 Finish
되지 않은) 완료된 작업일 수 있습니다. 비정상적인 작업은 (긴) 지정된 시간 동안 실행된 작업으로서, 아웃풋을 생성하지 않을 수도 있습니다.
마지막으로, 아래 Runner 실행자에 단계를 통합하기 위해 Run
/Follow*
/Finish
API의 실행을 조정하고, Follow*
호출이 연결을 잃은 경우 단계 실행자 서비스에 다시 연결하고 처리를 할 수 있도록 클라이언트 라이브러리를 제공하는 것이 좋습니다.
RunRequest Parameters
RunRequest.Steps
필드에 단계가 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>
이는 이 병합 요청에서 Runner에 도입된 동일한 로그 형식입니다. 이 형식을 생성하기 위해 사용된 로깅 라이브러리는 GitLab Runner
와 Step Runner
간에 공유되어야 합니다.
마스킹
Step Runner
는 민감한 변수나 토큰들을 마스킹하는 것에 대한 책임이 있습니다. 이 작업은 위의 로그 형식으로 형식화되기 전에 이루어져야 합니다. 변수를 마스킹하는 데 사용된 라이브러리는 GitLab Runner
와 Step Runner
사이에서 공유되어야 합니다. (아래의 관련 모듈을 참조하세요.)
프록시 명령
Step Runner
바이너리에는 (일반적으로 텍스트 기반) stdin
/stdout
/stderr
기반 프로토콜의 데이터를 gRPC 서비스로 프록시하는 명령이 포함될 것입니다. 이 명령은 gRPC 서비스가 실행 중인 동일한 호스트에서 실행되며, stdin
에서 입력을 읽어서 동일한 소켓을 통해 gRPC 서비스로 전달하고, 해당 서비스로부터의 출력을 동일한 소켓을 통해 수신하여 이를 stdout
/stderr
를 통해 클라이언트에 전달합니다. 이 명령을 통해 클라이언트 (Runner와 같은)는 stdin
/stderr
/stdout
기반 프로토콜을 통해 gRPC
서비스로 투명하게 터널링할 수 있으며, 이를 통해 도커 이미지에서 Step Runner
서비스의 gRPC 포트를 노출할 필요가 없어지거나 VM에서 SSH 포트 포워딩을 설정할 필요가 없어지며, Runner가 설립된 프로토콜 (즉, SSH 및 docker exec
)을 사용하여 Step Runner
와 상호 작용할 수 있게 됩니다. stdout
은 Step Runner
서비스로부터의 응답 작성을 위해 예약되어야 하며, stderr
은 프록시
명령 자체에서 발생하는 오류를 위해 예약되어야 합니다.
Executors
다음은 각 Runner 실행 프로그램에서 Step Runner에 연결할 방법입니다:
인스턴스
인스턴스 실행 프로그램은 현재와 같이 SSH를 통해 접근됩니다. 그러나 명령어를 파이핑하고 bash 셸을 시작하는 대신 프록시 명령을 호출하게 되며, 이 명령은 다시 알려진 위치에 있는 Step Runner 소켓에 연결합니다. Runner는 그러면 직접 gRPC
호출을 수행하고 SSH
연결을 통해 gRPC
서비스에 투명하게 터널링할 수 있습니다. 이것은 Runner가 VM에서 VM을 만들기 위해 전용 Mac 인스턴스의 중첩 서버를 호출하는 방법과 동일합니다.
이는 Step Runner가 작업 실행 환경에 존재하고 시작되어 있어야 한다는 조건이 필요합니다.
Docker
같은 요구사항이 Docker 실행 프로그램에도 적용됩니다 (그리고 docker-autoscaler
에도). 그러나 컨테이너 내에서 gRPC
서비스에 연결하려면 Runner는 컨테이너 내부의 gRPC
서비스에 docker exec
를 수행하고 해당 프록시 명령을 실행하여 컨테이너 내부의 gRPC
서비스에 연결하게 됩니다. 클라이언트는 그럼 docker exec
의 stdin
로 작성할 수 있으며, 이는 자동적으로 gRPC
서비스로 프록시가 될 것이며, 해당 서비스로부터의 응답이 담긴 stdout/stderr
에서 읽게 됩니다.
쿠버네티스
쿠버네티스 노드의 Kubelet은 실행 중인 Pod의 컨테이너에서 프로세스를 시작할 exec API를 노출합니다. 이를 사용하여 Docker executor와 유사하게 Pod 내부에서 gRPC
호출을 할 수 있는 브릿지 프로세스를 exec create
할 것입니다.
이 보호된 Kubelet API에 액세스하려면 Pod의 exec 하위 리소스를 제공하는 Kubernetes API를 사용해야 합니다. 호출자는 /exec
로 끝나는 Pod의 URL에 POST할 수 있으며, 그런 다음 연결을 양방향 바이트 스트리밍을 위해 SPDY 프로토콜로 협상할 수 있습니다. 따라서 GitLab Runner는 Kubernetes API를 사용하여 Step Runner 서비스에 연결하고 작업 페이로드를 전달할 수 있습니다.
이것은 kubectl exec
가 작동하는 방식과 동일합니다. 사실, SPDY 협상과 같은 대부분의 내부 기능은 client-go
라이브러리에서 제공됩니다. 따라서 Runner는 필요한 라이브러리를 가져와서 Kubectl을 사용하는 대신 직접 Kubernetes API를 호출할 수 있습니다.
과거 쿠버네티스 Executor의 약점 중 하나는 하나의 exec를 통해 전체 작업을 실행하는 것이었습니다. 이를 완화하기 위해 Runner는 “존재하는 쉘 프로세스에 다시 연결할 수 있는” attach 명령을 사용합니다.
그러나 Step Runner의 경우 이것은 필요하지 않습니다. 왜냐하면 exec는 장기간 실행되는 gRPC 프로세스에 대한 브릿지를 설정하기 때문입니다. 연결이 끊겨도 Runner는 다른 연결을 exec하여 follow
와 같은 RPC 호출을 계속할 것입니다.