m/n/k/hyperkube: avoid unnecessary rebuilds
Previously, hyperkube was rebuilt each time a commit was made in the
monorepo. This change stops this by reading the variables from a
filtered stamp file instead. Now, only this filtered file is rebuilt
each time, which is very fast compared to linking hyperkube.
Previously, volatile status variables were used for gitTreeState and
buildDate. But the volatile status is bad for reproducibility, as it
makes Bazel intentionally use stale caches. Instead, these variables
are now only defined in release builds, and left unstamped during
development. These variables are available at the /version endpoint of
the apiserver, so there may be some utility in defining them for release
builds, but they are not needed during development.
The buildDate is now taken from the commit date instead of
SOURCE_DATE_EPOCH, which simplifies the build process as we don't need
to define that variable anymore.
Previously, KUBERNETES_gitCommit was referenced but not defined by the
status script. It is now defined as the monorepo commit, which is more
useful than leaving it blank.
Change-Id: I6228888507e400ca1f53167ee9d4f132f5711a45
Reviewed-on: https://review.monogon.dev/c/monogon/+/4167
Tested-by: Jenkins CI
Reviewed-by: Tim Windelschmidt <tim@monogon.tech>
diff --git a/build/BUILD.bazel b/build/BUILD.bazel
new file mode 100644
index 0000000..20cfd57
--- /dev/null
+++ b/build/BUILD.bazel
@@ -0,0 +1,17 @@
+load("//build/filter_stamp:def.bzl", "filtered_stamp")
+
+# This is a filtered stable status file which contains only the variables which
+# do not change on every commit. Using this instead of the status file avoids
+# unnecessary rebuilds.
+filtered_stamp(
+ name = "stabler_status",
+ vars = [
+ "KUBERNETES_gitMajor",
+ "KUBERNETES_gitMinor",
+ "KUBERNETES_gitVersion",
+ "KUBERNETES_gitCommit",
+ "KUBERNETES_gitTreeState",
+ "KUBERNETES_buildDate",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/build/print-workspace-status.py b/build/print-workspace-status.py
index 9eb551f..d54333d 100755
--- a/build/print-workspace-status.py
+++ b/build/print-workspace-status.py
@@ -21,11 +21,8 @@
# only within the context of some product.
from dataclasses import dataclass
-from datetime import datetime, timezone
-import os
import re
import subprocess
-import time
from typing import Optional
@@ -45,18 +42,17 @@
subprocess.check_output(["git", "rev-parse", "HEAD^{commit}"]).decode().strip()
)
+# Git commit date.
+git_commit_date: str = (
+ subprocess.check_output(["git", "show", "--pretty=format:%cI", "--no-patch", "HEAD"]).decode().strip()
+)
+
# Git tags pointing at this commit.
git_tags_b: [bytes] = subprocess.check_output(
["git", "tag", "--sort=-version:refname", "--points-at", "HEAD"]
).split(b"\n")
git_tags: [str] = [t.decode().strip() for t in git_tags_b if t.decode().strip() != ""]
-# Build timestamp, respecting SOURCE_DATE_EPOCH for reproducible builds.
-build_timestamp = int(time.time())
-sde = os.environ.get("SOURCE_DATE_EPOCH")
-if sde is not None:
- build_timestamp = int(sde)
-
variables["STABLE_MONOGON_gitCommit"] = git_commit
variables["STABLE_MONOGON_gitTreeState"] = git_tree_state
@@ -94,6 +90,10 @@
return Version(product, version, [])
+# Is this a release build of the given product?
+is_release: dict[str, bool] = {}
+
+
for product in ["metropolis", "cloud"]:
# Get exact version from tags.
version = None
@@ -102,6 +102,8 @@
if version is not None:
break
+ is_release[product] = version is not None and git_tree_state == "clean"
+
if version is None:
# No exact version found. Use latest tag for the given product and
# append a 'devXXX' identifier based on number of commits since that
@@ -193,16 +195,17 @@
if not kubernetes_version_parsed:
raise Exception("invalid Kubernetes version: " + kubernetes_version)
-# The Kubernetes build tree is considered clean iff the monorepo build tree is
-# considered clean.
-variables["KUBERNETES_gitTreeState"] = git_tree_state
-variables["KUBERNETES_buildDate"] = datetime.fromtimestamp(
- build_timestamp, timezone.utc
-).strftime("%Y-%m-%dT%H:%M:%SZ")
variables["STABLE_KUBERNETES_gitMajor"] = kubernetes_version_parsed[1]
variables["STABLE_KUBERNETES_gitMinor"] = kubernetes_version_parsed[2]
variables["STABLE_KUBERNETES_gitVersion"] = kubernetes_version + "+mngn"
+# Stamp commit info into Kubernetes only for release builds, to avoid
+# unnecessary rebuilds of hyperkube during development.
+if is_release["metropolis"]:
+ variables["STABLE_KUBERNETES_gitCommit"] = git_commit
+ variables["STABLE_KUBERNETES_gitTreeState"] = git_tree_state
+ variables["STABLE_KUBERNETES_buildDate"] = git_commit_date
+
# Emit variables to stdout for consumption by Bazel and targets.
for key in sorted(variables.keys()):
print("{} {}".format(key, variables[key]))
diff --git a/metropolis/node/kubernetes/hyperkube/BUILD.bazel b/metropolis/node/kubernetes/hyperkube/BUILD.bazel
index 9fb4c22..3930eb8 100644
--- a/metropolis/node/kubernetes/hyperkube/BUILD.bazel
+++ b/metropolis/node/kubernetes/hyperkube/BUILD.bazel
@@ -1,5 +1,4 @@
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
-load(":kubernetes_version_def.bzl", "version_x_defs")
go_library(
name = "hyperkube_lib",
@@ -18,9 +17,24 @@
],
)
+stamp_pkgs = [
+ "k8s.io/component-base/version",
+ "k8s.io/client-go/pkg/version",
+]
+
+stamp_vars = [
+ "gitMajor",
+ "gitMinor",
+ "gitVersion",
+ "gitCommit",
+ "gitTreeState",
+ "buildDate",
+]
+
go_binary(
name = "hyperkube",
embed = [":hyperkube_lib"],
+ stampsrcs = ["//build:stabler_status"],
visibility = ["//metropolis/node:__pkg__"],
- x_defs = version_x_defs(),
+ x_defs = {"%s.%s" % (pkg, var): "{STABLER_KUBERNETES_%s}" % var for pkg in stamp_pkgs for var in stamp_vars},
)
diff --git a/metropolis/node/kubernetes/hyperkube/kubernetes_version_def.bzl b/metropolis/node/kubernetes/hyperkube/kubernetes_version_def.bzl
deleted file mode 100644
index ddd1347..0000000
--- a/metropolis/node/kubernetes/hyperkube/kubernetes_version_def.bzl
+++ /dev/null
@@ -1,48 +0,0 @@
-# Copyright 2020 The Monogon Project Authors.
-#
-# SPDX-License-Identifier: Apache-2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# This reimplements k8s.io/kubernetes-compatible version_x_defs, while namespacing
-# stamp variables with KUBERNETES_*.
-# The generated build defs then are defines by the workspace status script, see
-# //build/print-workspace-status.sh.
-
-def version_x_defs():
- stamp_pkgs = [
- "k8s.io/component-base/version",
- "k8s.io/client-go/pkg/version",
- ]
-
- stamp_vars = [
- "buildDate",
- "gitCommit",
- "gitTreeState",
- ]
-
- stable_stamp_vars = [
- "gitMajor",
- "gitMinor",
- "gitVersion",
- ]
-
- # Generate the cross-product.
- x_defs = {}
- for pkg in stamp_pkgs:
- for var in stamp_vars:
- x_defs["%s.%s" % (pkg, var)] = "{KUBERNETES_%s}" % var
- for pkg in stamp_pkgs:
- for var in stable_stamp_vars:
- x_defs["%s.%s" % (pkg, var)] = "{STABLE_KUBERNETES_%s}" % var
- return x_defs