1212from tornado .ioloop import IOLoop
1313from tornado .log import app_log
1414
15+ from .utils import rendezvous_rank
16+
1517
1618class Build :
1719 """Represents a build of a git repository into a docker image.
@@ -36,7 +38,7 @@ class Build:
3638 """
3739 def __init__ (self , q , api , name , namespace , repo_url , ref , git_credentials , build_image ,
3840 image_name , push_secret , memory_limit , docker_host , node_selector ,
39- appendix = '' , log_tail_lines = 100 ):
41+ appendix = '' , log_tail_lines = 100 , sticky_builds = False ):
4042 self .q = q
4143 self .api = api
4244 self .repo_url = repo_url
@@ -56,6 +58,10 @@ def __init__(self, q, api, name, namespace, repo_url, ref, git_credentials, buil
5658 self .stop_event = threading .Event ()
5759 self .git_credentials = git_credentials
5860
61+ self .sticky_builds = sticky_builds
62+
63+ self ._component_label = "binderhub-build"
64+
5965 def get_cmd (self ):
6066 """Get the cmd to run to build the image"""
6167 cmd = [
@@ -144,8 +150,71 @@ def progress(self, kind, obj):
144150 """Put the current action item into the queue for execution."""
145151 self .main_loop .add_callback (self .q .put , {'kind' : kind , 'payload' : obj })
146152
153+ def get_affinity (self ):
154+ """Determine the affinity term for the build pod.
155+
156+ There are a two affinity strategies, which one is used depends on how
157+ the BinderHub is configured.
158+
159+ In the default setup the affinity of each build pod is an "anti-affinity"
160+ which causes the pods to prefer to schedule on separate nodes.
161+
162+ In a setup with docker-in-docker enabled pods for a particular
163+ repository prefer to schedule on the same node in order to reuse the
164+ docker layer cache of previous builds.
165+ """
166+ dind_pods = self .api .list_namespaced_pod (
167+ self .namespace ,
168+ label_selector = "component=dind,app=binder" ,
169+ )
170+
171+ if self .sticky_builds and dind_pods :
172+ node_names = [pod .spec .node_name for pod in dind_pods .items ]
173+ ranked_nodes = rendezvous_rank (node_names , self .repo_url )
174+ best_node_name = ranked_nodes [0 ]
175+
176+ affinity = client .V1Affinity (
177+ node_affinity = client .V1NodeAffinity (
178+ preferred_during_scheduling_ignored_during_execution = [
179+ client .V1PreferredSchedulingTerm (
180+ weight = 100 ,
181+ preference = client .V1NodeSelectorTerm (
182+ match_fields = [
183+ client .V1NodeSelectorRequirement (
184+ key = "metadata.name" ,
185+ operator = "In" ,
186+ values = [best_node_name ],
187+ )
188+ ]
189+ ),
190+ )
191+ ]
192+ )
193+ )
194+
195+ else :
196+ affinity = client .V1Affinity (
197+ pod_anti_affinity = client .V1PodAntiAffinity (
198+ preferred_during_scheduling_ignored_during_execution = [
199+ client .V1WeightedPodAffinityTerm (
200+ weight = 100 ,
201+ pod_affinity_term = client .V1PodAffinityTerm (
202+ topology_key = "kubernetes.io/hostname" ,
203+ label_selector = client .V1LabelSelector (
204+ match_labels = dict (
205+ component = self ._component_label
206+ )
207+ )
208+ )
209+ )
210+ ]
211+ )
212+ )
213+
214+ return affinity
215+
147216 def submit (self ):
148- """Submit a image spec to openshift's s2i and wait for completion """
217+ """Submit a build pod to create the image for the repository. """
149218 volume_mounts = [
150219 client .V1VolumeMount (mount_path = "/var/run/docker.sock" , name = "docker-socket" )
151220 ]
@@ -166,13 +235,12 @@ def submit(self):
166235 if self .git_credentials :
167236 env .append (client .V1EnvVar (name = 'GIT_CREDENTIAL_ENV' , value = self .git_credentials ))
168237
169- component_label = "binderhub-build"
170238 self .pod = client .V1Pod (
171239 metadata = client .V1ObjectMeta (
172240 name = self .name ,
173241 labels = {
174242 "name" : self .name ,
175- "component" : component_label ,
243+ "component" : self . _component_label ,
176244 },
177245 annotations = {
178246 "binder-repo" : self .repo_url ,
@@ -211,23 +279,7 @@ def submit(self):
211279 node_selector = self .node_selector ,
212280 volumes = volumes ,
213281 restart_policy = "Never" ,
214- affinity = client .V1Affinity (
215- pod_anti_affinity = client .V1PodAntiAffinity (
216- preferred_during_scheduling_ignored_during_execution = [
217- client .V1WeightedPodAffinityTerm (
218- weight = 100 ,
219- pod_affinity_term = client .V1PodAffinityTerm (
220- topology_key = "kubernetes.io/hostname" ,
221- label_selector = client .V1LabelSelector (
222- match_labels = dict (
223- component = component_label
224- )
225- )
226- )
227- )
228- ]
229- )
230- )
282+ affinity = self .get_affinity ()
231283 )
232284 )
233285
0 commit comments