diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 1ca780142..8482e4489 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -91,6 +91,7 @@ - [Kubernetes](lib/k8s/KUBERNETES.md) - [K8s Remote Run](lib/k8s/REMOTE_RUN.md) - [K8s Tutorial](lib/k8s/TUTORIAL.md) + - [k8s chain.link labels](lib/k8s/labels.md) - [Config](lib/config/config.md) - [CRIB Connector](lib/crib.md) --- diff --git a/book/src/lib/k8s/REMOTE_RUN.md b/book/src/lib/k8s/REMOTE_RUN.md index 6453c5cea..6a8fede4f 100644 --- a/book/src/lib/k8s/REMOTE_RUN.md +++ b/book/src/lib/k8s/REMOTE_RUN.md @@ -39,6 +39,9 @@ export TEST_ENV_VAR=myTestVarForAJob # your image to run as a k8s job ACCOUNT=$(aws sts get-caller-identity | jq -r .Account) export ENV_JOB_IMAGE="${ACCOUNT}.dkr.ecr.us-west-2.amazonaws.com/core-integration-tests:v1.1" +export DETACH_RUNNER=true # if you want the test job to run in the background after it has started +export CHAINLINK_ENV_USER=yourUser # user to run the tests +export CHAINLINK_USER_TEAM=yourTeam # team to run the tests for # your example test file to run inside k8s # if ENV_JOB_IMAGE is present it will create a job, wait until it finished and get logs go run examples/remote-test-runner/env.go diff --git a/book/src/lib/k8s/TUTORIAL.md b/book/src/lib/k8s/TUTORIAL.md index f5f3ba471..60b10f24d 100644 --- a/book/src/lib/k8s/TUTORIAL.md +++ b/book/src/lib/k8s/TUTORIAL.md @@ -52,6 +52,9 @@ func main() { err := environment.New(&environment.Config{ KeepConnection: false, RemoveOnInterrupt: false, + Labels: []string{"chain.link/product=myProduct", "chain.link/team=my-team", "chain.link/cost-center=test-tooling-load-test"}, + WorkloadLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"}, + PodLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"}, }). AddHelm(ethereum.New(nil)). AddHelm(chainlink.New(0, nil)). @@ -66,6 +69,11 @@ Then run `go run examples/simple/env.go` Now you have your environment running, you can [connect](#connect-to-environment) to it later +> [!NOTE] +> `chain.link/*` labels are used for internal reporting and cost allocation. They are strictly required and validated. You won't be able to create a new environment without them. +> In this tutorial we create almost all of them manually, but there are convenience functions to do it for you. +> You can read more about labels [here](./labels.md) + ## Connect to environment We've already created an environment [previously](#getting-started), now we can connect @@ -84,7 +92,7 @@ You can get the namespace name from logs on creation time ## Debugging a new integration environment -You can spin up environment and block on forwarder if you'd like to run some other code +You can spin up environment and block on forwarder if you'd like to run some other code. Let's use convenience functions for creating `chain.link` labels. ```golang package main @@ -96,8 +104,17 @@ import ( ) func main() { + nsLabels, err := GetRequiredChainLinkNamespaceLabels("my-product", "load") + require.NoError(t, err, "Error creating required chain.link labels for namespace") + + workloadPodLabels, err := GetRequiredChainLinkWorkloadAndPodLabels("my-product", "load") + require.NoError(t, err, "Error creating required chain.link labels for workloads and pods") + + nsLabels := append(nsLabel,s "type=construction-in-progress") err := environment.New(&environment.Config{ - Labels: []string{"type=construction-in-progress"}, + Labels: nsLabels, + WorkloadLabels: workloadPodLabels + PodLabels: workloadPodLabels NamespacePrefix: "new-environment", KeepConnection: true, RemoveOnInterrupt: true, @@ -134,6 +151,8 @@ type ConnectedChart interface { GetValues() *map[string]any // ExportData export deployment part data in the env ExportData(e *Environment) error + // GetLabels get labels for component, it must return `chain.link/component` label + GetLabels() map[string]string } ``` @@ -153,6 +172,12 @@ func New(props *Props) environment.ConnectedChart { Props: props, } } + +func (m NewDeploymentPart) GetLabels() map[string]string { + return map[string]string{ + "chain.link/component": "new-deployment-part", + } +} ``` Now let's tie them together @@ -169,6 +194,9 @@ import ( func main() { e := environment.New(&environment.Config{ + Labels: []string{"chain.link/product=myProduct", "chain.link/team=my-team", "chain.link/cost-center=test-tooling-load-test"}, + WorkloadLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"}, + PodLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"}, NamespacePrefix: "adding-new-deployment-part", TTL: 3 * time.Hour, KeepConnection: true, @@ -220,6 +248,8 @@ type ConnectedChart interface { GetValues() *map[string]any // ExportData export deployment part data in the env ExportData(e *Environment) error + // GetLabels get labels for component, it must return `chain.link/component` label + GetLabels() map[string]string } ``` @@ -269,7 +299,12 @@ import ( ) func main() { - e := environment.New(nil) + envConfig := &environment.Config{ + Labels: []string{"chain.link/product=myProduct", "chain.link/team=my-team", "chain.link/cost-center=test-tooling-load-test"}, + WorkloadLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"}, + PodLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"} + } + e := environment.New(envConfig) err := e. AddChart(blockscout.New(&blockscout.Props{})). // you can also add cdk8s charts if you like Go code AddHelm(ethereum.New(nil)). @@ -322,10 +357,13 @@ import ( ) func main() { - e := environment.New(&environment.Config{ - NamespacePrefix: "modified-env", - Labels: []string{fmt.Sprintf("envType=Modified")}, - }). + modifiedEnvConfig := &environment.Config{ + NamespacePrefix: "modified-env", + Labels: []string{"envType=Modified", "chain.link/product=myProduct", "chain.link/team=my-team", "chain.link/cost-center=test-tooling-load-test"}, + WorkloadLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"}, + PodLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"} + } + e := environment.New(modifiedEnvConfig). AddChart(blockscout.New(&blockscout.Props{ WsURL: "ws://geth:8546", HttpURL: "http://geth:8544", @@ -371,16 +409,19 @@ import ( ) func main() { - e := environment.New(&environment.Config{ - NamespacePrefix: "modified-env", - Labels: []string{fmt.Sprintf("envType=Modified")}, - }). - AddHelm(mockservercfg.New(nil)). - AddHelm(mockserver.New(nil)). - AddHelm(ethereum.New(nil)). - AddHelm(chainlink.New(0, map[string]any{ - "replicas": 1, - })) + modifiedEnvConfig := &environment.Config{ + NamespacePrefix: "modified-env", + Labels: []string{"envType=Modified", "chain.link/product=myProduct", "chain.link/team=my-team", "chain.link/cost-center=test-tooling-load-test"}, + WorkloadLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"}, + PodLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"} + } + e := environment.New(modifiedEnvConfig). + AddHelm(mockservercfg.New(nil)). + AddHelm(mockserver.New(nil)). + AddHelm(ethereum.New(nil)). + AddHelm(chainlink.New(0, map[string]any{ + "replicas": 1, + })) err := e.Run() if err != nil { panic(err) @@ -427,6 +468,10 @@ const ( EnvVarUserDescription = "Owner of an environment" EnvVarUserExample = "Satoshi" + EnvVarTeam = "CHAINLINK_USER_TEAM" + EnvVarTeamDescription = "Team to, which owner of the environment belongs to" + EnvVarTeamExample = "BIX, CCIP, BCM" + EnvVarCLCommitSha = "CHAINLINK_COMMIT_SHA" EnvVarCLCommitShaDescription = "The sha of the commit that you're running tests on. Mostly used for CI" EnvVarCLCommitShaExample = "${{ github.sha }}" @@ -466,22 +511,55 @@ type Config struct { Namespace string // Labels is a set of labels applied to the namespace in a format of "key=value" Labels []string - nsLabels *map[string]*string - // ReadyCheckData is settings for readiness probes checks for all deployment components - // checking that all pods are ready by default with 8 minutes timeout - // &client.ReadyCheckData{ - // ReadinessProbeCheckSelector: "", - // Timeout: 15 * time.Minute, - // } - ReadyCheckData *client.ReadyCheckData - // DryRun if true, app will just generate a manifest in local dir - DryRun bool - // InsideK8s used for long-running soak tests where you connect to env from the inside - InsideK8s bool - // KeepConnection keeps connection until interrupted with a signal, useful when prototyping and debugging a new env - KeepConnection bool - // RemoveOnInterrupt automatically removes an environment on interrupt - RemoveOnInterrupt bool + // PodLabels is a set of labels applied to every pod in the namespace + PodLabels map[string]string + // WorkloadLabels is a set of labels applied to every workload in the namespace + WorkloadLabels map[string]string + // PreventPodEviction if true sets a k8s annotation safe-to-evict=false to prevent pods from being evicted + // Note: This should only be used if your test is completely incapable of handling things like K8s rebalances without failing. + // If that is the case, it's worth the effort to make your test fault-tolerant soon. The alternative is expensive and infuriating. + PreventPodEviction bool + // Allow deployment to nodes with these tolerances + Tolerations []map[string]string + // Restrict deployment to only nodes matching a particular node role + NodeSelector map[string]string + // ReadyCheckData is settings for readiness probes checks for all deployment components + // checking that all pods are ready by default with 8 minutes timeout + // &client.ReadyCheckData{ + // ReadinessProbeCheckSelector: "", + // Timeout: 15 * time.Minute, + // } + ReadyCheckData *client.ReadyCheckData + // DryRun if true, app will just generate a manifest in local dir + DryRun bool + // InsideK8s used for long-running soak tests where you connect to env from the inside + InsideK8s bool + // SkipManifestUpdate will skip updating the manifest upon connecting to the environment. Should be true if you wish to update the manifest (e.g. upgrade pods) + SkipManifestUpdate bool + // KeepConnection keeps connection until interrupted with a signal, useful when prototyping and debugging a new env + KeepConnection bool + // RemoveOnInterrupt automatically removes an environment on interrupt + RemoveOnInterrupt bool + // UpdateWaitInterval an interval to wait for deployment update started + UpdateWaitInterval time.Duration + + // Remote Runner Specific Variables // + // JobImage an image to run environment as a job inside k8s + JobImage string + // Specify only if you want remote-runner to start with a specific name + RunnerName string + // Specify only if you want to mount reports from test run in remote runner + ReportPath string + // JobLogFunction a function that will be run on each log + JobLogFunction func(*Environment, string) + // Test the testing library current Test struct + Test *testing.T + // jobDeployed used to limit us to 1 remote runner deploy + jobDeployed bool + // detachRunner should we detach the remote runner after starting the test + detachRunner bool + // fundReturnFailed the status of a fund return + fundReturnFailed bool } ``` @@ -501,7 +579,11 @@ import ( ) func main() { - e := environment.New(nil). + e := environment.New(&environment.Config{ + Labels: []string{"chain.link/product=myProduct", "chain.link/team=my-team", "chain.link/cost-center=test-tooling-load-test"}, + WorkloadLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"}, + PodLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"} + }). AddHelm(ethereum.New(nil)). AddHelm(chainlink.New(0, nil)) if err := e.Run(); err != nil { @@ -530,9 +612,11 @@ import ( ) func main() { - e := environment.New(&environment.Config{ - Labels: []string{fmt.Sprintf("envType=%s", pkg.EnvTypeEVM5)}, - }). + e := environment.New(&environment.Config{ + Labels: []string{fmt.Sprintf("envType=%s", pkg.EnvTypeEVM5), "chain.link/product=myProduct", "chain.link/team=my-team", "chain.link/cost-center=test-tooling-load-test"}, + WorkloadLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"}, + PodLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"} + }). AddHelm(ethereum.New(nil)). AddHelm(chainlink.New(0, nil)) err := e.Run() @@ -583,7 +667,12 @@ import ( ) func main() { - e := environment.New(nil). + envConfig := &environment.Config{ + Labels: []string{"chain.link/product=myProduct", "chain.link/team=my-team", "chain.link/cost-center=test-tooling-load-test"}, + WorkloadLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"}, + PodLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"} + } + e := environment.New(envConfig). AddChart(goc.New()). AddChart(dummy.New()) if err := e.Run(); err != nil { diff --git a/book/src/lib/k8s/labels.md b/book/src/lib/k8s/labels.md new file mode 100644 index 000000000..0807bffee --- /dev/null +++ b/book/src/lib/k8s/labels.md @@ -0,0 +1,67 @@ +# k8s `chain.link` Labels + +## Purpose +Resource labeling has been introduced to better associate Kubernetes (k8s) costs with products and teams. This document describes the labels used in the k8s cluster. + +## Required Labels +Labels should be applied to all resources in the k8s cluster at three levels: +- **Namespace** +- **Workload** +- **Pod** + +All three levels should include the following labels: +- `chain.link/team` - Name of the team that owns the resource. +- `chain.link/product` - Product that the resource belongs to. +- `chain.link/cost-center` - Product and framework name. + +Additionally, pods should include the following label: +- `chain.link/component` - Name of the component. + +### `chain.link/team` +This label represents the team responsible for the resource, but it might not be the team of the individual who created the resource. It should reflect the team the environment is **created for**. + +For example, if you are a member of the Test Tooling team, but someone from the BIX team requests load tests, the namespace should be labeled as: `chain.link/team: bix`. + +### `chain.link/product` +This label specifies the product the resource belongs to. Internally, some products may have alternative names (e.g., OCR instead of Data Feeds). To standardize data analysis, use the following names: + +``` +automation +bcm +ccip +data-feedsv1.0 +data-feedsv2.0 +data-feedsv3.0 +data-streamsv0.3 +data-streamsv1.0 +deco +functions +proof-of-reserve +scale +staking +vrf +``` + +For example: +- OCR version 1: `data-feedsv1.0` +- OCR version 2: `data-feedsv2.0` + +### `chain.link/cost-center` +This label serves as an umbrella for specific test or environment types and should rarely change. For load or soak tests using solutions provided by the Test Tooling team, use the convention: `test-tooling--test` + +For example: `test-tooling-load-test`. + +This allows easy distinction from load tests run using other tools. + +### `chain.link/component` +This label identifies different components within the same product. Examples include: +- `chainlink` - Chainlink node. +- `geth` - Go-Ethereum blockchain node. +- `test-runner` - Remote test runner. + +## Adding Labels to New Components +Adding a new component to an existing framework is discouraged. The recommended approach is to add the component to CRIB and make these labels part of the deployment templates. + +If you need to add a new component, refer to the following sections in the [k8s Tutorial](./TUTORIAL.md): +- **Creating a new deployment part in Helm** +- **Creating a new deployment part in cdk8s** \ No newline at end of file diff --git a/book/src/libs/wasp/k8s.md b/book/src/libs/wasp/k8s.md new file mode 100644 index 000000000..d749e727e --- /dev/null +++ b/book/src/libs/wasp/k8s.md @@ -0,0 +1 @@ +# k8s