Skip to content

Commit 5c555e8

Browse files
authored
Merge pull request #11 from jianxiaoguo/main
chore(builder): run imagebuider replace pod with job
2 parents 0b1d2d4 + 1148502 commit 5c555e8

7 files changed

Lines changed: 124 additions & 80 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ rootfs/usr/bin/
44
./builder
55
coverage.txt
66
testdata/hooks/pre-receive
7+
.idea/
8+

charts/builder/templates/builder-deployment.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ spec:
6464
value: "{{ .Values.global.storage }}"
6565
- name: "DRYCC_REGISTRY_LOCATION"
6666
value: "{{ .Values.global.registry_location }}"
67+
- name: "TTL_SECONDS_AFTER_FINISHED"
68+
value: "{{ .Values.global.ttl_seconds_after_finished }}"
6769
# Set GIT_LOCK_TIMEOUT to number of minutes you want to wait to git push again to the same repository
6870
- name: "GIT_LOCK_TIMEOUT"
6971
value: "30"

charts/builder/templates/builder-role.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,7 @@ rules:
1616
- apiGroups: [""]
1717
resources: ["pods/log"]
1818
verbs: ["get"]
19+
- apiGroups: ["batch"]
20+
resources: ["jobs"]
21+
verbs: ["create"]
1922
{{- end -}}

charts/builder/values.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ service:
1111
# limits_memory: "50Mi"
1212
# builder_pod_node_selector: "disk:ssd"
1313

14+
# When the TTL controller cleans up the Job. default: 6h
15+
# see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#ttl-mechanism-for-finished-jobs
16+
ttl_seconds_after_finished: 21600
17+
1418
global:
1519
# Role-Based Access Control for Kubernetes >= 1.5
1620
use_rbac: false

pkg/gitreceive/build.go

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ func build(
148148
securityContext := k8s.SecurityContextFromPrivileged(true)
149149

150150
imageName := fmt.Sprintf("%s:git-%s", appName, gitSha.Short())
151-
buildPodName := imagebuilderPodName(appName, gitSha.Short())
151+
buildJobName := imagebuilderJobName(appName, gitSha.Short())
152152
registryLocation := conf.RegistryLocation
153153
builderImageEnv := make(map[string]string)
154154
if registryLocation != "on-cluster" {
@@ -160,9 +160,9 @@ func build(
160160
builderImageEnv["DRYCC_STACK"] = stack["name"]
161161
builderImageEnv["DRYCC_REGISTRY_LOCATION"] = registryLocation
162162

163-
pod := createBuilderPod(
163+
job := createBuilderJob(
164164
conf.Debug,
165-
buildPodName,
165+
buildJobName,
166166
conf.PodNamespace,
167167
appConf.Values,
168168
tarKey,
@@ -181,17 +181,16 @@ func build(
181181

182182
log.Info("Starting build... but first, coffee!")
183183
log.Debug("Use image %s: %s", stack["name"], stack["image"])
184-
log.Debug("Starting pod %s", buildPodName)
185-
json, err := prettyPrintJSON(pod)
184+
log.Debug("Starting job %s", buildJobName)
185+
json, err := prettyPrintJSON(job)
186186
if err == nil {
187-
log.Debug("Pod spec: %v", json)
187+
log.Debug("Job spec: %v", json)
188188
} else {
189-
log.Debug("Error creating json representation of pod spec: %v", err)
189+
log.Debug("Error creating json representation of Job spec: %v", err)
190190
}
191+
jobsInterface := kubeClient.BatchV1().Jobs(conf.PodNamespace)
191192

192-
podsInterface := kubeClient.CoreV1().Pods(conf.PodNamespace)
193-
194-
newPod, err := podsInterface.Create(ctx.TODO(), pod, metav1.CreateOptions{})
193+
newJob, err := jobsInterface.Create(ctx.TODO(), job, metav1.CreateOptions{})
195194
if err != nil {
196195
return fmt.Errorf("creating builder pod (%s)", err)
197196
}
@@ -201,11 +200,19 @@ func build(
201200
defer close(stopCh)
202201
go pw.Controller.Run(stopCh)
203202

204-
if err := waitForPod(pw, newPod.Namespace, newPod.Name, conf.SessionIdleInterval(), conf.BuilderPodTickDuration(), conf.BuilderPodWaitDuration()); err != nil {
203+
if err := waitForPod(pw, newJob.Namespace, newJob.Name, conf.SessionIdleInterval(), conf.BuilderPodTickDuration(), conf.BuilderPodWaitDuration()); err != nil {
205204
return fmt.Errorf("watching events for builder pod startup (%s)", err)
206205
}
207206

208-
req := kubeClient.CoreV1().RESTClient().Get().Namespace(newPod.Namespace).Name(newPod.Name).Resource("pods").SubResource("log").VersionedParams(
207+
options := metav1.ListOptions{
208+
LabelSelector: fmt.Sprintf("heritage=%s", newJob.Name),
209+
}
210+
podList, err := kubeClient.CoreV1().Pods(newJob.Namespace).List(context.Background(), options)
211+
if err != nil {
212+
return fmt.Errorf("list pods %s fail: (%s)", newJob.Name, err)
213+
}
214+
215+
req := kubeClient.CoreV1().RESTClient().Get().Namespace(newJob.Namespace).Name(podList.Items[0].Name).Resource("pods").SubResource("log").VersionedParams(
209216
&corev1.PodLogOptions{
210217
Follow: true,
211218
}, scheme.ParameterCodec)
@@ -224,19 +231,19 @@ func build(
224231

225232
log.Debug(
226233
"Waiting for the %s/%s pod to end. Checking every %s for %s",
227-
newPod.Namespace,
228-
newPod.Name,
234+
newJob.Namespace,
235+
newJob.Name,
229236
conf.BuilderPodTickDuration(),
230237
conf.BuilderPodWaitDuration(),
231238
)
232239
// check the state and exit code of the build pod.
233240
// if the code is not 0 return error
234-
if err := waitForPodEnd(pw, newPod.Namespace, newPod.Name, conf.BuilderPodTickDuration(), conf.BuilderPodWaitDuration()); err != nil {
241+
if err := waitForPodEnd(pw, newJob.Namespace, newJob.Name, conf.BuilderPodTickDuration(), conf.BuilderPodWaitDuration()); err != nil {
235242
return fmt.Errorf("error getting builder pod status (%s)", err)
236243
}
237244
log.Debug("Done")
238245
log.Debug("Checking for builder pod exit code")
239-
buildPod, err := kubeClient.CoreV1().Pods(newPod.Namespace).Get(ctx.TODO(), newPod.Name, metav1.GetOptions{})
246+
buildPod, err := kubeClient.CoreV1().Pods(newJob.Namespace).Get(ctx.TODO(), podList.Items[0].Name, metav1.GetOptions{})
240247
if err != nil {
241248
return fmt.Errorf("error getting builder pod status (%s)", err)
242249
}

pkg/gitreceive/k8s_util.go

Lines changed: 64 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"os"
8+
"strconv"
79
"time"
810

911
"github.com/drycc/builder/pkg/k8s"
1012
"github.com/pborman/uuid"
13+
batchv1 "k8s.io/api/batch/v1"
1114
corev1 "k8s.io/api/core/v1"
1215
apierrors "k8s.io/apimachinery/pkg/api/errors"
1316
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -27,7 +30,7 @@ const (
2730
imagebuilderConfigPath = "/etc/imagebuilder"
2831
)
2932

30-
func imagebuilderPodName(appName, shortSha string) string {
33+
func imagebuilderJobName(appName, shortSha string) string {
3134
uid := uuid.New()[:8]
3235
// NOTE(bacongobbler): pod names cannot exceed 63 characters in length, so we truncate
3336
// the application name to stay under that limit when adding all the extra metadata to the name
@@ -37,7 +40,7 @@ func imagebuilderPodName(appName, shortSha string) string {
3740
return fmt.Sprintf("imagebuild-%s-%s-%s", appName, shortSha, uid)
3841
}
3942

40-
func createBuilderPod(
43+
func createBuilderJob(
4144
debug bool,
4245
name,
4346
namespace string,
@@ -54,9 +57,9 @@ func createBuilderPod(
5457
pullPolicy corev1.PullPolicy,
5558
securityContext corev1.SecurityContext,
5659
nodeSelector map[string]string,
57-
) *corev1.Pod {
60+
) *batchv1.Job {
5861

59-
pod := buildPod(debug, name, namespace, pullPolicy, securityContext, nodeSelector, env)
62+
job := buildJob(debug, name, namespace, pullPolicy, securityContext, nodeSelector, env)
6063

6164
// inject application envvars as a special envvar which will be handled by imagebuilder to
6265
// inject them as build-time variables.
@@ -67,47 +70,67 @@ func createBuilderPod(
6770
// So we need to translate the map into json.
6871
if _, ok := env["DRYCC_DOCKER_BUILD_ARGS_ENABLED"]; ok {
6972
imageBuildArgs, _ := json.Marshal(env)
70-
addEnvToPod(pod, "DOCKER_BUILD_ARGS", string(imageBuildArgs))
73+
addEnvToJob(job, "DOCKER_BUILD_ARGS", string(imageBuildArgs))
7174
}
75+
job.Spec.Template.Spec.Containers[0].Name = builderName
76+
job.Spec.Template.Spec.Containers[0].Image = builderImage
7277

73-
pod.Spec.Containers[0].Name = builderName
74-
pod.Spec.Containers[0].Image = builderImage
75-
76-
addEnvToPod(pod, tarPath, tarKey)
77-
addEnvToPod(pod, sourceVersion, gitShortHash)
78-
addEnvToPod(pod, "IMG_NAME", imageName)
79-
addEnvToPod(pod, builderStorage, storageType)
78+
addEnvToJob(job, tarPath, tarKey)
79+
addEnvToJob(job, sourceVersion, gitShortHash)
80+
addEnvToJob(job, "IMG_NAME", imageName)
81+
addEnvToJob(job, builderStorage, storageType)
8082
// inject existing DRYCC_REGISTRY_PROXY_HOST and PORT info to imagebuilder
8183
// see https://github.com/drycc/imagebuilder/issues/83
82-
addEnvToPod(pod, "DRYCC_REGISTRY_PROXY_HOST", registryHost)
83-
addEnvToPod(pod, "DRYCC_REGISTRY_PROXY_PORT", registryPort)
84+
addEnvToJob(job, "DRYCC_REGISTRY_PROXY_HOST", registryHost)
85+
addEnvToJob(job, "DRYCC_REGISTRY_PROXY_PORT", registryPort)
8486

8587
for key, value := range builderImageEnv {
86-
addEnvToPod(pod, key, value)
88+
addEnvToJob(job, key, value)
8789
}
8890

89-
return &pod
91+
return &job
92+
}
93+
94+
func newInt32(i int32) *int32 {
95+
return &i
9096
}
9197

92-
func buildPod(
98+
func buildJob(
9399
debug bool,
94100
name,
95101
namespace string,
96102
//pullPolicy api.PullPolicy,
97103
pullPolicy corev1.PullPolicy,
98104
securityContext corev1.SecurityContext,
99105
nodeSelector map[string]string,
100-
env map[string]interface{}) corev1.Pod {
101-
pod := corev1.Pod{
102-
Spec: corev1.PodSpec{
103-
RestartPolicy: corev1.RestartPolicyNever,
104-
Containers: []corev1.Container{
105-
{
106-
ImagePullPolicy: pullPolicy,
107-
SecurityContext: &securityContext,
106+
env map[string]interface{}) batchv1.Job {
107+
TTLSecondsAfterFinished := newInt32(21600)
108+
if os.Getenv("TTL_SECONDS_AFTER_FINISHED") != "" {
109+
ttl, err := strconv.ParseInt(os.Getenv("TTL_SECONDS_AFTER_FINISHED"), 10, 32)
110+
if err == nil {
111+
TTLSecondsAfterFinished = newInt32(int32(ttl))
112+
}
113+
}
114+
115+
job := batchv1.Job{
116+
Spec: batchv1.JobSpec{
117+
BackoffLimit: newInt32(0),
118+
TTLSecondsAfterFinished: TTLSecondsAfterFinished,
119+
Template: corev1.PodTemplateSpec{
120+
ObjectMeta: metav1.ObjectMeta{
121+
Labels: map[string]string{
122+
"heritage": name,
123+
},
124+
},
125+
Spec: corev1.PodSpec{
126+
Containers: []corev1.Container{
127+
{
128+
ImagePullPolicy: pullPolicy,
129+
SecurityContext: &securityContext,
130+
},
131+
},
108132
},
109133
},
110-
Volumes: []corev1.Volume{},
111134
},
112135
ObjectMeta: metav1.ObjectMeta{
113136
Name: name,
@@ -117,8 +140,10 @@ func buildPod(
117140
},
118141
},
119142
}
120-
121-
pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{
143+
job.Spec.Template.Spec.RestartPolicy = corev1.RestartPolicyNever
144+
job.Spec.Template.Spec.Containers[0].ImagePullPolicy = pullPolicy
145+
job.Spec.Template.Spec.Containers[0].SecurityContext = &securityContext
146+
job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes, corev1.Volume{
122147
Name: objectStore,
123148
VolumeSource: corev1.VolumeSource{
124149
Secret: &corev1.SecretVolumeSource{
@@ -127,14 +152,14 @@ func buildPod(
127152
},
128153
})
129154

130-
pod.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{
155+
job.Spec.Template.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{
131156
{
132157
Name: objectStore,
133158
MountPath: objectStorePath,
134159
ReadOnly: true,
135160
},
136161
}
137-
pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{
162+
job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes, corev1.Volume{
138163
Name: imagebuilderConfig,
139164
VolumeSource: corev1.VolumeSource{
140165
ConfigMap: &corev1.ConfigMapVolumeSource{
@@ -145,35 +170,35 @@ func buildPod(
145170
},
146171
})
147172

148-
pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
173+
job.Spec.Template.Spec.Containers[0].VolumeMounts = append(job.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
149174
Name: imagebuilderConfig,
150175
MountPath: imagebuilderConfigPath,
151176
ReadOnly: true,
152177
})
153178

154-
if len(pod.Spec.Containers) > 0 {
179+
if len(job.Spec.Template.Spec.Containers) > 0 {
155180
for k, v := range env {
156-
pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, corev1.EnvVar{
181+
job.Spec.Template.Spec.Containers[0].Env = append(job.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
157182
Name: k,
158183
Value: fmt.Sprintf("%v", v),
159184
})
160185
}
161186
}
162187

163188
if len(nodeSelector) > 0 {
164-
pod.Spec.NodeSelector = nodeSelector
189+
job.Spec.Template.Spec.NodeSelector = nodeSelector
165190
}
166191

167192
if debug {
168-
addEnvToPod(pod, debugKey, "1")
193+
addEnvToJob(job, debugKey, "1")
169194
}
170195

171-
return pod
196+
return job
172197
}
173198

174-
func addEnvToPod(pod corev1.Pod, key, value string) {
175-
if len(pod.Spec.Containers) > 0 {
176-
pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, corev1.EnvVar{
199+
func addEnvToJob(job batchv1.Job, key, value string) {
200+
if len(job.Spec.Template.Spec.Containers) > 0 {
201+
job.Spec.Template.Spec.Containers[0].Env = append(job.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
177202
Name: key,
178203
Value: value,
179204
})

0 commit comments

Comments
 (0)