Skip to content

Commit 1f3cd44

Browse files
committed
[WIP] Add support for dedicated chaincode nodes
See #228 Signed-off-by: James Taylor <[email protected]>
1 parent fd880c0 commit 1f3cd44

File tree

8 files changed

+146
-21
lines changed

8 files changed

+146
-21
lines changed

cmd/run/main.go

Lines changed: 67 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import (
99
"github.com/hyperledger-labs/fabric-builder-k8s/internal/builder"
1010
"github.com/hyperledger-labs/fabric-builder-k8s/internal/log"
1111
"github.com/hyperledger-labs/fabric-builder-k8s/internal/util"
12-
"k8s.io/apimachinery/pkg/api/validation"
12+
apivalidation "k8s.io/apimachinery/pkg/api/validation"
13+
"k8s.io/apimachinery/pkg/util/validation"
1314
)
1415

1516
const (
@@ -19,22 +20,7 @@ const (
1920
maximumKubeNamePrefixLength = 30
2021
)
2122

22-
func main() {
23-
debug := util.GetOptionalEnv(util.DebugVariable, "false")
24-
ctx := log.NewCmdContext(context.Background(), debug == "true")
25-
logger := log.New(ctx)
26-
27-
if len(os.Args) != expectedArgsLength {
28-
logger.Println("Expected BUILD_OUTPUT_DIR and RUN_METADATA_DIR arguments")
29-
os.Exit(1)
30-
}
31-
32-
buildOutputDirectory := os.Args[buildOutputDirectoryArg]
33-
runMetadataDirectory := os.Args[runMetadataDirectoryArg]
34-
35-
logger.Debugf("Build output directory: %s", buildOutputDirectory)
36-
logger.Debugf("Run metadata directory: %s", runMetadataDirectory)
37-
23+
func getPeerID(logger *log.CmdLogger) string {
3824
peerID, err := util.GetRequiredEnv(util.PeerIDVariable)
3925
if err != nil {
4026
logger.Printf("Expected %s environment variable\n", util.PeerIDVariable)
@@ -43,41 +29,102 @@ func main() {
4329

4430
logger.Debugf("%s=%s", util.PeerIDVariable, peerID)
4531

32+
return peerID
33+
}
34+
35+
func getKubeconfigPath(logger *log.CmdLogger) string {
4636
kubeconfigPath := util.GetOptionalEnv(util.KubeconfigPathVariable, "")
4737
logger.Debugf("%s=%s", util.KubeconfigPathVariable, kubeconfigPath)
4838

39+
return kubeconfigPath
40+
}
41+
42+
func getKubeNamespace(logger *log.CmdLogger) string {
4943
kubeNamespace := util.GetOptionalEnv(util.ChaincodeNamespaceVariable, "")
5044
logger.Debugf("%s=%s", util.ChaincodeNamespaceVariable, kubeNamespace)
5145

5246
if kubeNamespace == "" {
47+
var err error
48+
5349
kubeNamespace, err = util.GetKubeNamespace()
5450
if err != nil {
51+
logger.Debugf("Error getting namespace: %+v\n", util.DefaultNamespace, err)
5552
kubeNamespace = util.DefaultNamespace
5653
}
54+
55+
logger.Debugf("Using default namespace: %s\n", util.DefaultNamespace)
5756
}
5857

58+
return kubeNamespace
59+
}
60+
61+
func getKubeNodeRole(logger *log.CmdLogger) string {
62+
kubeNodeRole := util.GetOptionalEnv(util.ChaincodeNodeRoleVariable, "")
63+
logger.Debugf("%s=%s", util.ChaincodeNodeRoleVariable, kubeNodeRole)
64+
65+
// TODO: are valid taint values the same?!
66+
if msgs := validation.IsValidLabelValue(kubeNodeRole); len(msgs) > 0 {
67+
logger.Printf("The %s environment variable must be a valid Kubernetes label value: %s", util.ChaincodeNodeRoleVariable, msgs[0])
68+
os.Exit(1)
69+
}
70+
71+
return kubeNodeRole
72+
}
73+
74+
func getKubeServiceAccount(logger *log.CmdLogger) string {
5975
kubeServiceAccount := util.GetOptionalEnv(util.ChaincodeServiceAccountVariable, util.DefaultServiceAccountName)
6076
logger.Debugf("%s=%s", util.ChaincodeServiceAccountVariable, kubeServiceAccount)
6177

78+
return kubeServiceAccount
79+
}
80+
81+
func getKubeNamePrefix(logger *log.CmdLogger) string {
6282
kubeNamePrefix := util.GetOptionalEnv(util.ObjectNamePrefixVariable, util.DefaultObjectNamePrefix)
6383
logger.Debugf("%s=%s", util.ObjectNamePrefixVariable, kubeNamePrefix)
6484

6585
if len(kubeNamePrefix) > maximumKubeNamePrefixLength {
66-
logger.Printf("The FABRIC_K8S_BUILDER_OBJECT_NAME_PREFIX environment variable must be a maximum of 30 characters")
86+
logger.Printf("The %s environment variable must be a maximum of 30 characters", util.ObjectNamePrefixVariable)
6787
os.Exit(1)
6888
}
6989

70-
if msgs := validation.NameIsDNS1035Label(kubeNamePrefix, true); len(msgs) > 0 {
71-
logger.Printf("The FABRIC_K8S_BUILDER_OBJECT_NAME_PREFIX environment variable must be a valid DNS-1035 label: %s", msgs[0])
90+
if msgs := apivalidation.NameIsDNS1035Label(kubeNamePrefix, true); len(msgs) > 0 {
91+
logger.Printf("The %s environment variable must be a valid DNS-1035 label: %s", util.ObjectNamePrefixVariable, msgs[0])
7292
os.Exit(1)
7393
}
7494

95+
return kubeNamePrefix
96+
}
97+
98+
func main() {
99+
debug := util.GetOptionalEnv(util.DebugVariable, "false")
100+
ctx := log.NewCmdContext(context.Background(), debug == "true")
101+
logger := log.New(ctx)
102+
103+
if len(os.Args) != expectedArgsLength {
104+
logger.Println("Expected BUILD_OUTPUT_DIR and RUN_METADATA_DIR arguments")
105+
os.Exit(1)
106+
}
107+
108+
buildOutputDirectory := os.Args[buildOutputDirectoryArg]
109+
runMetadataDirectory := os.Args[runMetadataDirectoryArg]
110+
111+
logger.Debugf("Build output directory: %s", buildOutputDirectory)
112+
logger.Debugf("Run metadata directory: %s", runMetadataDirectory)
113+
114+
peerID := getPeerID(logger)
115+
kubeconfigPath := getKubeconfigPath(logger)
116+
kubeNamespace := getKubeNamespace(logger)
117+
kubeNodeRole := getKubeNodeRole(logger)
118+
kubeServiceAccount := getKubeServiceAccount(logger)
119+
kubeNamePrefix := getKubeNamePrefix(logger)
120+
75121
run := &builder.Run{
76122
BuildOutputDirectory: buildOutputDirectory,
77123
RunMetadataDirectory: runMetadataDirectory,
78124
PeerID: peerID,
79125
KubeconfigPath: kubeconfigPath,
80126
KubeNamespace: kubeNamespace,
127+
KubeNodeRole: kubeNodeRole,
81128
KubeServiceAccount: kubeServiceAccount,
82129
KubeNamePrefix: kubeNamePrefix,
83130
}

cmd/run/main_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,28 @@ var _ = Describe("Main", func() {
4545
),
4646
)
4747

48+
DescribeTable("Running the run command produces the correct error for invalid FABRIC_K8S_BUILDER_NODE_ROLE environment variable values",
49+
func(kubeNodeRoleValue, expectedErrorMessage string) {
50+
args := []string{"BUILD_OUTPUT_DIR", "RUN_METADATA_DIR"}
51+
command := exec.Command(runCmdPath, args...)
52+
command.Env = append(os.Environ(),
53+
"CORE_PEER_ID=core-peer-id-abcdefghijklmnopqrstuvwxyz-0123456789",
54+
"FABRIC_K8S_BUILDER_NODE_ROLE="+kubeNodeRoleValue,
55+
)
56+
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
57+
Expect(err).NotTo(HaveOccurred())
58+
59+
Eventually(session).Should(gexec.Exit(1))
60+
Eventually(
61+
session.Err,
62+
).Should(gbytes.Say(expectedErrorMessage))
63+
},
64+
Entry("When the FABRIC_K8S_BUILDER_NODE_ROLE is too long", "long-node-role-is-looooooooooooooooooooooooooooooooooooooooooong", `run \[\d+\]: The FABRIC_K8S_BUILDER_NODE_ROLE environment variable must be a valid Kubernetes label value: must be no more than 63 characters`),
65+
Entry("When the FABRIC_K8S_BUILDER_NODE_ROLE contains invalid characters", "invalid*value", `run \[\d+\]: The FABRIC_K8S_BUILDER_NODE_ROLE environment variable must be a valid Kubernetes label value: a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '\.', and must start and end with an alphanumeric character`),
66+
Entry("When the FABRIC_K8S_BUILDER_NODE_ROLE does not start with an alphanumeric character", ".role", `run \[\d+\]: The FABRIC_K8S_BUILDER_NODE_ROLE environment variable must be a valid Kubernetes label value: a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '\.', and must start and end with an alphanumeric character`),
67+
Entry("When the FABRIC_K8S_BUILDER_NODE_ROLE does not end with an alphanumeric character", "role-", `run \[\d+\]: The FABRIC_K8S_BUILDER_NODE_ROLE environment variable must be a valid Kubernetes label value: a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '\.', and must start and end with an alphanumeric character`),
68+
)
69+
4870
DescribeTable("Running the run command produces the correct error for invalid FABRIC_K8S_BUILDER_OBJECT_NAME_PREFIX environment variable values",
4971
func(kubeNamePrefixValue, expectedErrorMessage string) {
5072
args := []string{"BUILD_OUTPUT_DIR", "RUN_METADATA_DIR"}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Dedicated nodes
2+
3+
TBC
4+
5+
The `FABRIC_K8S_BUILDER_NODE_ROLE` environment variable can be used to...
6+
7+
For example, if `FABRIC_K8S_BUILDER_NODE_ROLE` is set to `chaincode`, ... using the following command.
8+
9+
```shell
10+
kubectl label nodes node1 fabric-builder-k8s-role=chaincode
11+
kubectl taint nodes node1 fabric-builder-k8s-role=chaincode:NoSchedule
12+
```
13+
14+
More complex requirements should be handled with Dynamic Admission Control using a Mutating Webhook.
15+
For example, it looks like the namespace-node-affinity webhook could be used to assign node affinity and tolerations to all pods in the FABRIC_K8S_BUILDER_NAMESPACE namespace.

docs/configuring/overview.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ External builders are configured in the `core.yaml` file, for example:
1414
- CORE_PEER_ID
1515
- FABRIC_K8S_BUILDER_DEBUG
1616
- FABRIC_K8S_BUILDER_NAMESPACE
17+
- FABRIC_K8S_BUILDER_NODE_ROLE
1718
- FABRIC_K8S_BUILDER_OBJECT_NAME_PREFIX
1819
- FABRIC_K8S_BUILDER_SERVICE_ACCOUNT
1920
- KUBERNETES_SERVICE_HOST
@@ -30,6 +31,7 @@ The k8s builder is configured using the following environment variables.
3031
| ------------------------------------- | -------------------------------- | ---------------------------------------------------- |
3132
| CORE_PEER_ID | | The Fabric peer ID (required) |
3233
| FABRIC_K8S_BUILDER_NAMESPACE | The peer namespace or `default` | The Kubernetes namespace to run chaincode with |
34+
| FABRIC_K8S_BUILDER_NODE_ROLE | | TBC |
3335
| FABRIC_K8S_BUILDER_OBJECT_NAME_PREFIX | `hlfcc` | Eye-catcher prefix for Kubernetes object names |
3436
| FABRIC_K8S_BUILDER_SERVICE_ACCOUNT | `default` | The Kubernetes service account to run chaincode with |
3537
| FABRIC_K8S_BUILDER_DEBUG | `false` | Set to `true` to enable k8s builder debug messages |

internal/builder/run.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type Run struct {
1616
PeerID string
1717
KubeconfigPath string
1818
KubeNamespace string
19+
KubeNodeRole string
1920
KubeServiceAccount string
2021
KubeNamePrefix string
2122
}
@@ -73,6 +74,7 @@ func (r *Run) Run(ctx context.Context) error {
7374
kubeObjectName,
7475
r.KubeNamespace,
7576
r.KubeServiceAccount,
77+
r.KubeNodeRole,
7678
r.PeerID,
7779
chaincodeData,
7880
imageData,

internal/util/env.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
const (
1111
builderVariablePrefix = "FABRIC_K8S_BUILDER_"
1212
ChaincodeNamespaceVariable = builderVariablePrefix + "NAMESPACE"
13+
ChaincodeNodeRoleVariable = builderVariablePrefix + "NODE_ROLE"
1314
ObjectNamePrefixVariable = builderVariablePrefix + "OBJECT_NAME_PREFIX"
1415
ChaincodeServiceAccountVariable = builderVariablePrefix + "SERVICE_ACCOUNT"
1516
DebugVariable = builderVariablePrefix + "DEBUG"

internal/util/k8s.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ func CreateChaincodeJob(
365365
ctx context.Context,
366366
logger *log.CmdLogger,
367367
jobsClient typedBatchv1.JobInterface,
368-
objectName, namespace, serviceAccount, peerID string,
368+
objectName, namespace, serviceAccount, nodeRole, peerID string,
369369
chaincodeData *ChaincodeJSON,
370370
imageData *ImageJSON,
371371
) (*batchv1.Job, error) {
@@ -381,6 +381,41 @@ func CreateChaincodeJob(
381381
return nil, fmt.Errorf("error getting chaincode job definition for chaincode ID %s: %w", chaincodeData.ChaincodeID, err)
382382
}
383383

384+
if nodeRole != "" {
385+
logger.Debugf(
386+
"Adding node affinity and toleration to job definition for chaincode ID %s: %s",
387+
chaincodeData.ChaincodeID,
388+
nodeRole,
389+
)
390+
391+
jobDefinition.Spec.Template.Spec.Affinity = &apiv1.Affinity{
392+
NodeAffinity: &apiv1.NodeAffinity{
393+
RequiredDuringSchedulingIgnoredDuringExecution: &apiv1.NodeSelector{
394+
NodeSelectorTerms: []apiv1.NodeSelectorTerm{
395+
{
396+
MatchExpressions: []apiv1.NodeSelectorRequirement{
397+
{
398+
Key: "fabric-builder-k8s-role",
399+
Operator: apiv1.NodeSelectorOpIn,
400+
Values: []string{nodeRole},
401+
},
402+
},
403+
},
404+
},
405+
},
406+
},
407+
}
408+
409+
jobDefinition.Spec.Template.Spec.Tolerations = []apiv1.Toleration{
410+
{
411+
Key: "fabric-builder-k8s-role",
412+
Operator: apiv1.TolerationOpEqual,
413+
Value: nodeRole,
414+
Effect: apiv1.TaintEffectNoSchedule,
415+
},
416+
}
417+
}
418+
384419
jobName := jobDefinition.ObjectMeta.Name
385420

386421
logger.Debugf(

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ nav:
106106
- Kubernetes permissions: configuring/kubernetes-permissions.md
107107
- Kubernetes namespace: configuring/kubernetes-namespace.md
108108
- Kubernetes service account: configuring/kubernetes-service-account.md
109+
- Dedicated nodes: configuring/dedicated-nodes.md
109110
- Tutorials:
110111
- Developing and debuging chaincode: tutorials/develop-chaincode.md
111112
- Creating a chaincode package: tutorials/package-chaincode.md

0 commit comments

Comments
 (0)