mirror of
https://github.com/clastix/kamaji.git
synced 2026-02-27 16:23:54 +00:00
Compare commits
85 Commits
helm-v0.3.
...
v0.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b594b598b1 | ||
|
|
c8ce212730 | ||
|
|
714b173132 | ||
|
|
0217d579d6 | ||
|
|
c242f4ac58 | ||
|
|
d4d25a8a05 | ||
|
|
cff7f7c4e5 | ||
|
|
6c817fd7ab | ||
|
|
d31ada4da6 | ||
|
|
ee01f721d2 | ||
|
|
912e010363 | ||
|
|
e2b03ca873 | ||
|
|
dccf7bd540 | ||
|
|
25a65a7496 | ||
|
|
1ff03246c6 | ||
|
|
8335f645a5 | ||
|
|
70a791be74 | ||
|
|
b0293c23b5 | ||
|
|
50bba9bb2e | ||
|
|
f05f7eaf07 | ||
|
|
bfd34ef47e | ||
|
|
b73c7a20ed | ||
|
|
004441e77e | ||
|
|
0f85b6c534 | ||
|
|
b674738f0d | ||
|
|
6dc3cd1876 | ||
|
|
96a57fefa5 | ||
|
|
87b6f75f66 | ||
|
|
1b24806fa3 | ||
|
|
f32ba4a76b | ||
|
|
19d91aa4d2 | ||
|
|
a4e2ac24ac | ||
|
|
0dffd9ba46 | ||
|
|
90b2ca1bab | ||
|
|
df8ca7c1d1 | ||
|
|
65519d4f22 | ||
|
|
e0fa8169f1 | ||
|
|
41eddc0462 | ||
|
|
1a9a8a1854 | ||
|
|
0c8a16d604 | ||
|
|
b7adb314ad | ||
|
|
e55e6cfdd4 | ||
|
|
6388bf0a7f | ||
|
|
e089f0ad9a | ||
|
|
0b0bf09813 | ||
|
|
00ea4a562d | ||
|
|
2a33844c68 | ||
|
|
606926ec9a | ||
|
|
84b70b3b59 | ||
|
|
4ca79ceb4c | ||
|
|
8df8aa445a | ||
|
|
8da916b5cd | ||
|
|
f15eeebe02 | ||
|
|
7002d48ef9 | ||
|
|
79edd2606a | ||
|
|
650c20be2b | ||
|
|
7862717772 | ||
|
|
08eed7b244 | ||
|
|
1a561758b6 | ||
|
|
12f12832f7 | ||
|
|
b4d0f9b698 | ||
|
|
14624af093 | ||
|
|
52cdc90b48 | ||
|
|
fbb6e4eec5 | ||
|
|
880a29f543 | ||
|
|
b0b4ef95c6 | ||
|
|
bd909d6567 | ||
|
|
fcc10c95b2 | ||
|
|
7e912ed2e8 | ||
|
|
2374176faf | ||
|
|
aceeced53a | ||
|
|
53c9102ef3 | ||
|
|
15e1cf7d80 | ||
|
|
f853f25195 | ||
|
|
5acdc4cc41 | ||
|
|
360e8200cb | ||
|
|
b0c6972873 | ||
|
|
682006f8aa | ||
|
|
d59f494a69 | ||
|
|
7602d5d803 | ||
|
|
4c04edbfe8 | ||
|
|
cce4225e07 | ||
|
|
10f0021780 | ||
|
|
b99a685e2d | ||
|
|
a8de97e442 |
8
.github/workflows/ci.yaml
vendored
8
.github/workflows/ci.yaml
vendored
@@ -12,11 +12,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.18'
|
||||
check-latest: true
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2.3.0
|
||||
uses: golangci/golangci-lint-action@v3.2.0
|
||||
with:
|
||||
version: v1.49.0
|
||||
only-new-issues: false
|
||||
@@ -28,9 +29,10 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.18'
|
||||
check-latest: true
|
||||
- run: make yaml-installation-file
|
||||
- name: Checking if YAML installer file is not aligned
|
||||
run: if [[ $(git diff | wc -l) -gt 0 ]]; then echo ">>> Untracked generated files have not been committed" && git --no-pager diff && exit 1; fi
|
||||
|
||||
3
.github/workflows/e2e.yaml
vendored
3
.github/workflows/e2e.yaml
vendored
@@ -34,9 +34,10 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.18'
|
||||
check-latest: true
|
||||
- run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y golang-cfssl
|
||||
|
||||
10
.github/workflows/helm.yaml
vendored
10
.github/workflows/helm.yaml
vendored
@@ -8,6 +8,16 @@ on:
|
||||
branches: [ "*" ]
|
||||
|
||||
jobs:
|
||||
diff:
|
||||
name: diff
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: make -C charts/kamaji docs
|
||||
- name: Checking if Helm docs is not aligned
|
||||
run: if [[ $(git diff | wc -l) -gt 0 ]]; then echo ">>> Untracked changes have not been committed" && git --no-pager diff && exit 1; fi
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
@@ -14,9 +14,6 @@ linters:
|
||||
- wrapcheck
|
||||
- gomnd
|
||||
- scopelint
|
||||
- golint
|
||||
- interfacer
|
||||
- maligned
|
||||
- varnamelen
|
||||
- testpackage
|
||||
- tagliatelle
|
||||
@@ -38,6 +35,14 @@ linters:
|
||||
- gochecknoinits
|
||||
- funlen
|
||||
- dupl
|
||||
- maintidx
|
||||
- cyclop
|
||||
# deprecated linters
|
||||
- deadcode
|
||||
- golint
|
||||
- interfacer
|
||||
- structcheck
|
||||
- varcheck
|
||||
- nosnakecase
|
||||
- ifshort
|
||||
- maligned
|
||||
enable-all: true
|
||||
|
||||
@@ -22,6 +22,7 @@ COPY main.go main.go
|
||||
COPY api/ api/
|
||||
COPY controllers/ controllers/
|
||||
COPY internal/ internal/
|
||||
COPY indexers/ indexers/
|
||||
|
||||
# Build
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go build \
|
||||
|
||||
26
Makefile
26
Makefile
@@ -3,7 +3,7 @@
|
||||
# To re-generate a bundle for another specific version without changing the standard setup, you can:
|
||||
# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2)
|
||||
# - use environment variables to overwrite this value (e.g export VERSION=0.0.2)
|
||||
VERSION ?= 0.0.1
|
||||
VERSION ?= 0.1.1
|
||||
|
||||
# CHANNELS define the bundle channels used in the bundle.
|
||||
# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable")
|
||||
@@ -36,9 +36,7 @@ IMAGE_TAG_BASE ?= clastix.io/operator
|
||||
BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION)
|
||||
|
||||
# Image URL to use all building/pushing image targets
|
||||
IMG ?= clastix/kamaji:latest
|
||||
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
|
||||
CRD_OPTIONS ?= "crd:trivialVersions=true,preserveUnknownFields=false"
|
||||
IMG ?= clastix/kamaji:v$(VERSION)
|
||||
|
||||
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
|
||||
ifeq (,$(shell go env GOBIN))
|
||||
@@ -87,22 +85,33 @@ kind: ## Download kind locally if necessary.
|
||||
|
||||
CONTROLLER_GEN = $(shell pwd)/bin/controller-gen
|
||||
controller-gen: ## Download controller-gen locally if necessary.
|
||||
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.6.1)
|
||||
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.9.2)
|
||||
|
||||
GOLANGCI_LINT = $(shell pwd)/bin/golangci-lint
|
||||
golangci-lint: ## Download golangci-lint locally if necessary.
|
||||
$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint@v1.49.0)
|
||||
|
||||
KUSTOMIZE = $(shell pwd)/bin/kustomize
|
||||
kustomize: ## Download kustomize locally if necessary.
|
||||
$(call install-kustomize,$(KUSTOMIZE),3.8.7)
|
||||
|
||||
APIDOCS_GEN = $(shell pwd)/bin/crdoc
|
||||
apidocs-gen: ## Download crdoc locally if necessary.
|
||||
$(call go-install-tool,$(APIDOCS_GEN),fybrik.io/crdoc@latest)
|
||||
|
||||
##@ Development
|
||||
|
||||
manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
|
||||
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
|
||||
$(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
|
||||
cp config/crd/bases/kamaji.clastix.io_tenantcontrolplanes.yaml charts/kamaji/crds/tenantcontrolplane.yaml
|
||||
cp config/crd/bases/kamaji.clastix.io_datastores.yaml charts/kamaji/crds/datastore.yaml
|
||||
|
||||
generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
|
||||
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."
|
||||
|
||||
golint: golangci-lint ## Linting the code according to the styling guide.
|
||||
$(GOLANGCI_LINT) run -c .golangci.yml
|
||||
|
||||
test:
|
||||
go test ./... -coverprofile cover.out
|
||||
|
||||
@@ -247,3 +256,8 @@ env:
|
||||
e2e: env load helm ginkgo ## Create a KinD cluster, install Kamaji on it and run the test suite.
|
||||
$(HELM) upgrade --debug --install kamaji ./charts/kamaji --create-namespace --namespace kamaji-system --set "image.pullPolicy=Never"
|
||||
$(GINKGO) -v ./e2e
|
||||
|
||||
##@ Document
|
||||
|
||||
apidoc: apidocs-gen
|
||||
$(APIDOCS_GEN) crdoc --resources config/crd/bases --output docs/content/reference/api.md --template docs/templates/reference-cr.tmpl
|
||||
|
||||
62
README.md
62
README.md
@@ -20,39 +20,14 @@ Global hyper-scalers are leading the Managed Kubernetes space, while other cloud
|
||||
**Kamaji** aims to solve these pains by leveraging multi-tenancy and simplifying how to run multiple control planes on the same infrastructure with a fraction of the operational burden.
|
||||
|
||||
## How it works
|
||||
Kamaji turns any Kubernetes cluster into an _“admin cluster”_ to orchestrate other Kubernetes clusters called _“tenant clusters”_. What makes Kamaji special is that Control Planes of _“tenant clusters”_ are just regular pods running in the _“admin cluster”_ instead of dedicated Virtual Machines. This solution makes running control planes at scale cheaper and easier to deploy and operate. View [Core Concepts](./docs/concepts.md) for a deeper understanding of principles behind Kamaji's design.
|
||||
Kamaji turns any Kubernetes cluster into an _“admin cluster”_ to orchestrate other Kubernetes clusters called _“tenant clusters”_. What makes Kamaji special is that Control Planes of _“tenant clusters”_ are just regular pods running in the _“admin cluster”_ instead of dedicated Virtual Machines. This solution makes running control planes at scale cheaper and easier to deploy and operate.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/kamaji-light.png#gh-light-mode-only" />
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/kamaji-dark.png#gh-dark-mode-only" />
|
||||
</p>
|
||||
|
||||
All the tenant clusters built with Kamaji are fully compliant CNCF Kubernetes clusters and are compatible with the standard Kubernetes toolchains everybody knows and loves.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/screenshot.png" />
|
||||
</p>
|
||||

|
||||

|
||||
|
||||
## Getting started
|
||||
|
||||
Please refer to the [Getting Started guide](./docs/getting-started-with-kamaji.md) to deploy a minimal setup of Kamaji on KinD.
|
||||
|
||||
> This project is still in the early development stage which means it's not ready for production as APIs, commands, flags, etc. are subject to change, but also that your feedback can still help to shape it. Please try it out and let us know what you like, dislike, what works, what doesn't, etc.
|
||||
|
||||
## Use cases
|
||||
Kamaji project has been initially started as a solution for actual and common problems such as minimizing the Total Cost of Ownership while running Kubernetes at large scale. However, it can open a wider range of use cases.
|
||||
|
||||
Here are a few:
|
||||
|
||||
- **Managed Kubernetes:** enable companies to provide Cloud Native Infrastructure with ease by introducing a strong separation of concerns between management and workloads. Centralize clusters management, monitoring, and observability by leaving developers to focus on applications, increase productivity and reduce operational costs.
|
||||
- **Kubernetes as a Service:** provide Kubernetes clusters in a self-service fashion by running management and workloads on different infrastructures with the option of Bring Your Own Device, BYOD.
|
||||
- **Control Plane as a Service:** provide multiple Kubernetes control planes running on top of a single Kubernetes cluster. Tenants who use namespaces based isolation often still need access to cluster wide resources like Cluster Roles, Admission Webhooks, or Custom Resource Definitions.
|
||||
- **Edge Computing:** distribute Kubernetes workloads across edge computing locations without having to manage multiple clusters across various providers. Centralize management of hundreds of control planes while leaving workloads to run isolated on their own dedicated infrastructure.
|
||||
- **Cluster Simulation:** check new Kubernetes API or experimental flag or a new tool without impacting production operations. Kamaji will let you simulate such things in a safe and controlled environment.
|
||||
- **Workloads Testing:** check the behaviour of your workloads on different and multiple versions of Kubernetes with ease by deploying multiple Control Planes in a single cluster.
|
||||
Please refer to the [Getting Started guide](https://kamaji.clastix.io/getting-started/) to deploy a minimal setup of Kamaji on KinD.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -74,37 +49,16 @@ Here are a few:
|
||||
- [ ] Custom Prometheus metrics for monitoring and alerting
|
||||
- [x] `kine` integration for MySQL as datastore
|
||||
- [x] `kine` integration for PostgreSQL as datastore
|
||||
- [ ] Deeper `kubeadm` integration
|
||||
- [ ] Pooling of multiple `etcd` datastores
|
||||
- [x] Pool of multiple datastores
|
||||
- [ ] Automatic assigning of Tenant Control Plane to a datastore
|
||||
- [ ] Autoscaling of Tenant Control Plane pods
|
||||
|
||||
|
||||
## Documentation
|
||||
Please, check the project's [documentation](./docs/) for getting started with Kamaji.
|
||||
Please, check the project's [documentation](https://kamaji.clastix.io/) for getting started with Kamaji.
|
||||
|
||||
## Contributions
|
||||
Kamaji is Open Source with Apache 2 license and any contribution is welcome.
|
||||
|
||||
## Community
|
||||
Join the [Kubernetes Slack Workspace](https://slack.k8s.io/) and the [`#kamaji`](https://kubernetes.slack.com/archives/C03GLTTMWNN) channel to meet end-users and contributors.
|
||||
|
||||
## FAQs
|
||||
Q. What does Kamaji means?
|
||||
|
||||
A. Kamaji is named as the character _Kamaji_ from the Japanese movie [_Spirited Away_](https://en.wikipedia.org/wiki/Spirited_Away).
|
||||
|
||||
Q. Is Kamaji another Kubernetes distribution?
|
||||
|
||||
A. No, Kamaji is a Kubernetes Operator you can install on top of any Kubernetes cluster to provide hundreds of managed Kubernetes clusters as a service. We tested Kamaji on vanilla Kubernetes 1.22+, KinD, and Azure AKS. We expect it to work smoothly on other Kubernetes distributions. The tenant clusters made with Kamaji are conformant CNCF Kubernetes clusters as we leverage on [`kubeadm`](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/).
|
||||
|
||||
Q. Is it safe to run Kubernetes control plane components in a pod instead of dedicated virtual machines?
|
||||
|
||||
A. Yes, the tenant control plane components are packaged in the same way they are running in bare metal or virtual nodes. We leverage the `kubeadm` code to set up the control plane components as they were running on their own server. The unchanged images of upstream `kube-apiserver`, `kube-scheduler`, and `kube-controller-manager` are used.
|
||||
|
||||
Q. You already provide a Kubernetes multi-tenancy solution with [Capsule](https://capsule.clastix.io). Why does Kamaji matter?
|
||||
|
||||
A. A multi-tenancy solution, like Capsule shares the Kubernetes control plane among all tenants keeping tenant namespaces isolated by policies. While the solution is the right choice by balancing between features and ease of usage, there are cases where a tenant user requires access to the control plane, for example, when a tenant requires to manage CRDs on his own. With Kamaji, you can provide cluster admin permissions to the tenant.
|
||||
|
||||
Q. Well you convinced me, how to get a try?
|
||||
|
||||
A. It is possible to get started with Kamaji on a laptop with [KinD](./docs/getting-started-with-kamaji.md) installed.
|
||||
Join the [Kubernetes Slack Workspace](https://slack.k8s.io/) and the [`#kamaji`](https://kubernetes.slack.com/archives/C03GLTTMWNN) channel to meet end-users and contributors.
|
||||
@@ -75,10 +75,11 @@ type DataStoreSetupStatus struct {
|
||||
|
||||
// StorageStatus defines the observed state of StorageStatus.
|
||||
type StorageStatus struct {
|
||||
Driver string `json:"driver,omitempty"`
|
||||
Config DataStoreConfigStatus `json:"config,omitempty"`
|
||||
Setup DataStoreSetupStatus `json:"setup,omitempty"`
|
||||
Certificate DataStoreCertificateStatus `json:"certificate,omitempty"`
|
||||
Driver string `json:"driver,omitempty"`
|
||||
DataStoreName string `json:"dataStoreName,omitempty"`
|
||||
Config DataStoreConfigStatus `json:"config,omitempty"`
|
||||
Setup DataStoreSetupStatus `json:"setup,omitempty"`
|
||||
Certificate DataStoreCertificateStatus `json:"certificate,omitempty"`
|
||||
}
|
||||
|
||||
// KubeconfigStatus contains information about the generated kubeconfig.
|
||||
@@ -207,6 +208,8 @@ type KubernetesVersion struct {
|
||||
// KubernetesDeploymentStatus defines the status for the Tenant Control Plane Deployment in the management cluster.
|
||||
type KubernetesDeploymentStatus struct {
|
||||
appsv1.DeploymentStatus `json:",inline"`
|
||||
// Selector is the label selector used to group the Tenant Control Plane Pods used by the scale subresource.
|
||||
Selector string `json:"selector"`
|
||||
// The name of the Deployment for the given cluster.
|
||||
Name string `json:"name"`
|
||||
// The namespace which the Deployment for the given cluster is deployed.
|
||||
|
||||
@@ -85,6 +85,21 @@ type ControlPlaneComponentsResources struct {
|
||||
type DeploymentSpec struct {
|
||||
// +kubebuilder:default=2
|
||||
Replicas int32 `json:"replicas,omitempty"`
|
||||
// NodeSelector is a selector which must be true for the pod to fit on a node.
|
||||
// Selector which must match a node's labels for the pod to be scheduled on that node.
|
||||
// More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/
|
||||
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
|
||||
// If specified, the Tenant Control Plane pod's tolerations.
|
||||
// More info: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/
|
||||
Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
|
||||
// If specified, the Tenant Control Plane pod's scheduling constraints.
|
||||
// More info: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/
|
||||
Affinity *corev1.Affinity `json:"affinity,omitempty"`
|
||||
// TopologySpreadConstraints describes how the Tenant Control Plane pods ought to spread across topology
|
||||
// domains. Scheduler will schedule pods in a way which abides by the constraints.
|
||||
// In case of nil underlying LabelSelector, the Kamaji one for the given Tenant Control Plane will be used.
|
||||
// All topologySpreadConstraints are ANDed.
|
||||
TopologySpreadConstraints []corev1.TopologySpreadConstraint `json:"topologySpreadConstraints,omitempty"`
|
||||
// Resources defines the amount of memory and CPU to allocate to each component of the Control Plane
|
||||
// (kube-apiserver, controller-manager, and scheduler).
|
||||
Resources *ControlPlaneComponentsResources `json:"resources,omitempty"`
|
||||
@@ -110,20 +125,31 @@ type ServiceSpec struct {
|
||||
}
|
||||
|
||||
// AddonSpec defines the spec for every addon.
|
||||
type AddonSpec struct{}
|
||||
type AddonSpec struct {
|
||||
ImageOverrideTrait `json:",inline"`
|
||||
}
|
||||
|
||||
type ImageOverrideTrait struct {
|
||||
// ImageRepository sets the container registry to pull images from.
|
||||
// if not set, the default ImageRepository will be used instead.
|
||||
ImageRepository string `json:"imageRepository,omitempty"`
|
||||
// ImageTag allows to specify a tag for the image.
|
||||
// In case this value is set, kubeadm does not change automatically the version of the above components during upgrades.
|
||||
ImageTag string `json:"imageTag,omitempty"`
|
||||
}
|
||||
|
||||
// KonnectivitySpec defines the spec for Konnectivity.
|
||||
type KonnectivitySpec struct {
|
||||
// Port of Konnectivity proxy server.
|
||||
ProxyPort int32 `json:"proxyPort"`
|
||||
// Version for Konnectivity server and agent.
|
||||
// +kubebuilder:default=v0.0.31
|
||||
// +kubebuilder:default=v0.0.32
|
||||
Version string `json:"version,omitempty"`
|
||||
// ServerImage defines the container image for Konnectivity's server.
|
||||
// +kubebuilder:default=us.gcr.io/k8s-artifacts-prod/kas-network-proxy/proxy-server
|
||||
// +kubebuilder:default=registry.k8s.io/kas-network-proxy/proxy-server
|
||||
ServerImage string `json:"serverImage,omitempty"`
|
||||
// AgentImage defines the container image for Konnectivity's agent.
|
||||
// +kubebuilder:default=us.gcr.io/k8s-artifacts-prod/kas-network-proxy/proxy-agent
|
||||
// +kubebuilder:default=registry.k8s.io/kas-network-proxy/proxy-agent
|
||||
AgentImage string `json:"agentImage,omitempty"`
|
||||
// Resources define the amount of CPU and memory to allocate to the Konnectivity server.
|
||||
Resources *corev1.ResourceRequirements `json:"resources,omitempty"`
|
||||
@@ -131,13 +157,22 @@ type KonnectivitySpec struct {
|
||||
|
||||
// AddonsSpec defines the enabled addons and their features.
|
||||
type AddonsSpec struct {
|
||||
CoreDNS *AddonSpec `json:"coreDNS,omitempty"`
|
||||
// Enables the DNS addon in the Tenant Cluster.
|
||||
// The registry and the tag are configurable, the image is hard-coded to `coredns`.
|
||||
CoreDNS *AddonSpec `json:"coreDNS,omitempty"`
|
||||
// Enables the Konnectivity addon in the Tenant Cluster, required if the worker nodes are in a different network.
|
||||
Konnectivity *KonnectivitySpec `json:"konnectivity,omitempty"`
|
||||
KubeProxy *AddonSpec `json:"kubeProxy,omitempty"`
|
||||
// Enables the kube-proxy addon in the Tenant Cluster.
|
||||
// The registry and the tag are configurable, the image is hard-coded to `kube-proxy`.
|
||||
KubeProxy *AddonSpec `json:"kubeProxy,omitempty"`
|
||||
}
|
||||
|
||||
// TenantControlPlaneSpec defines the desired state of TenantControlPlane.
|
||||
type TenantControlPlaneSpec struct {
|
||||
// DataStore allows to specify a DataStore that should be used to store the Kubernetes data for the given Tenant Control Plane.
|
||||
// This parameter is optional and acts as an override over the default one which is used by the Kamaji Operator.
|
||||
// Migration from a different DataStore to another one is not yet supported and the reconciliation will be blocked.
|
||||
DataStore string `json:"dataStore,omitempty"`
|
||||
ControlPlane ControlPlane `json:"controlPlane"`
|
||||
// Kubernetes specification for tenant control plane
|
||||
Kubernetes KubernetesSpec `json:"kubernetes"`
|
||||
@@ -149,6 +184,7 @@ type TenantControlPlaneSpec struct {
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:subresource:scale:specpath=.spec.controlPlane.deployment.replicas,statuspath=.status.kubernetesResources.deployment.replicas,selectorpath=.status.kubernetesResources.deployment.selector
|
||||
// +kubebuilder:resource:shortName=tcp
|
||||
// +kubebuilder:printcolumn:name="Version",type="string",JSONPath=".spec.kubernetes.version",description="Kubernetes version"
|
||||
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.kubernetesResources.version.status",description="Kubernetes version"
|
||||
|
||||
@@ -61,6 +61,7 @@ func (in *AdditionalMetadata) DeepCopy() *AdditionalMetadata {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AddonSpec) DeepCopyInto(out *AddonSpec) {
|
||||
*out = *in
|
||||
out.ImageOverrideTrait = in.ImageOverrideTrait
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonSpec.
|
||||
@@ -520,6 +521,32 @@ func (in *DataStoreStatus) DeepCopy() *DataStoreStatus {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DeploymentSpec) DeepCopyInto(out *DeploymentSpec) {
|
||||
*out = *in
|
||||
if in.NodeSelector != nil {
|
||||
in, out := &in.NodeSelector, &out.NodeSelector
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.Tolerations != nil {
|
||||
in, out := &in.Tolerations, &out.Tolerations
|
||||
*out = make([]v1.Toleration, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Affinity != nil {
|
||||
in, out := &in.Affinity, &out.Affinity
|
||||
*out = new(v1.Affinity)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.TopologySpreadConstraints != nil {
|
||||
in, out := &in.TopologySpreadConstraints, &out.TopologySpreadConstraints
|
||||
*out = make([]v1.TopologySpreadConstraint, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Resources != nil {
|
||||
in, out := &in.Resources, &out.Resources
|
||||
*out = new(ControlPlaneComponentsResources)
|
||||
@@ -592,6 +619,21 @@ func (in *ExternalKubernetesObjectStatus) DeepCopy() *ExternalKubernetesObjectSt
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageOverrideTrait) DeepCopyInto(out *ImageOverrideTrait) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageOverrideTrait.
|
||||
func (in *ImageOverrideTrait) DeepCopy() *ImageOverrideTrait {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImageOverrideTrait)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *IngressSpec) DeepCopyInto(out *IngressSpec) {
|
||||
*out = *in
|
||||
|
||||
1
assets/kamaji-logo.svg
Normal file
1
assets/kamaji-logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="11.85 8.10 202.80 187.55"><title>Kamaji</title><path d="M32.1 13.7c-2.4.9-6.3 3.5-8.6 5.8-7.7 7.7-7.5 5-7.5 82.5 0 77.4-.2 74.8 7.5 82.5 7.7 7.8 4.2 7.5 90 7.5s82.3.3 90-7.5c7.7-7.7 7.5-5.1 7.5-82.5s.2-74.8-7.5-82.5c-7.8-7.8-4.1-7.5-90.4-7.4-66.7 0-77.2.3-81 1.6zm160.5 9.9c1.9.9 4.4 3.1 5.7 4.8l2.2 3.1v141l-2.2 3.1c-4.8 6.7-1.1 6.4-84.8 6.4s-80 .3-84.8-6.4l-2.2-3.1v-141l2.2-3.1c4.8-6.6.8-6.4 84.6-6.4 68 0 76.3.2 79.3 1.6z"/><path d="M90.1 33.7c-5.1 2.5-7.3 6.7-6.8 13.1.3 4.1 1 5.9 3.3 8.4s2.5 3 .9 2.3c-2-.7-25.1-4.6-29-4.9-1.1 0-2 .5-2 1.4 0 1.1-1.2 1.5-4.9 1.5-6.7 0-6.8 1.9-.4 4 8.2 2.7 9 3.4 3.3 3.5-5.3 0-8.2 1.1-7.1 2.8.7 1.2-2.7 2.2-8.1 2.2-7 0-6.5 2.4 1.1 5.1l3.9 1.4-2.9.5c-4.3.8-3.2 2.3 2.8 4.1l5.3 1.5-5.2 2.7c-8.2 4.2-8.3 5.8-.4 6.1 5.6.2 7.3 1.1 4.2 2.1-2.3.7-2.8 3.1-.9 3.7.7.3-.5 2-2.8 4-5.6 5.3-4 6.4 6.2 4.5 4.4-.8 8.1-1.3 8.3-1.2.2.2-1.3 2.4-3.3 4.8-2 2.4-3.6 4.7-3.6 5.2 0 .4 1.4.5 3 .3 2.9-.4 4 .5 2 1.7-.5.3-1 1.3-1 2.2 0 1.6 2.2 1.5 6.5-.3 1.7-.7 1.6-.2-.9 3-5.4 7.2.7 6.5 13.6-1.4 2.7-1.7 5.1-3 5.4-3 .3 0-.9 2.1-2.7 4.6-4.5 6.6-2.5 7.9 3.7 2.3 4.6-4.3 4.7-4.3 3-1.2-1.9 3.8-2.1 5.6-.4 5.1.6-.2 7.1-7.1 14.3-15.4 7.2-8.2 13.7-14.9 14.5-14.9.8 0 7.3 6.7 14.6 15 7.2 8.2 13.7 15.1 14.3 15.3 1.6.5 1.4-1.4-.5-5-1.6-3.2-1.6-3.2 3.2 1 6 5.1 7.8 4 3.5-2.2-1.8-2.5-3-4.6-2.7-4.6.3 0 2.7 1.3 5.4 3 12.9 7.9 19 8.6 13.6 1.4-2.5-3.2-2.6-3.7-.9-3 5.9 2.5 7.7 1.7 5.6-2.3-.9-1.5-.6-1.7 2-1.3 3.8.6 3.7-.5-.7-5.7-2-2.3-3.5-4.4-3.2-4.6.2-.2 2.1 0 4.3.4 13.9 3 16.4 1.8 9.8-4.3-2.1-1.9-3.2-3.6-2.5-3.6 2 0 1.4-2.8-.9-3.5-3.2-1-1.3-2 4.2-2.1 7.9-.2 7.8-1.9-.4-6.1l-5.2-2.7 5.4-1.6c6.4-1.8 7.9-4 2.9-4.1h-3.3l3.9-1.5c7.3-2.6 8.4-5.4 2.2-5.4-5.1 0-9.6-1.1-9-2.2 1.1-1.7-1.8-2.8-7.1-2.8-5.7-.1-4.9-.8 3.3-3.5 6.4-2.1 6.3-4-.4-4-3.7 0-4.9-.4-4.9-1.5 0-.9-.9-1.4-2-1.4-3.9.3-27 4.2-29 4.9-1.6.7-1.4.2.9-2.3 3.7-4 4.7-11.3 2.2-16.1-4.8-9.2-18.8-9.3-23.8 0-4.4 8.3.2 18.4 9.5 20.5 3 .6 2.8.8-5.5 4l-8.8 3.3-8.7-3.3c-8.1-3.2-8.4-3.4-5.5-4.1 1.7-.3 4.3-1.5 5.7-2.7 13.1-10.3.6-30.4-14.4-23.1zm77.6 98.4c-3.6 2.1-.8 7.7 3.2 6.4 2.1-.6 3.5-3.1 2.5-4.6-1.1-1.8-4-2.7-5.7-1.8zm8.3 3.9c0 1.9.5 2.1 6.3 1.8 4.7-.2 6.2-.7 6.2-1.8s-1.5-1.6-6.2-1.8c-5.8-.3-6.3-.1-6.3 1.8zm-135.6.3c-.2.7-.3 7.4-.2 14.8l.3 13.4 3.3.3c3.1.3 3.2.2 3.2-3.4 0-2.5.7-4.6 2.1-6l2.1-2.3 5 6c3.9 4.7 5.6 5.9 7.8 5.9 1.6 0 3.1-.3 3.3-.8.3-.4-2.1-4-5.4-8.1-3.2-4-5.9-7.6-5.9-8 0-.4 2.5-3.1 5.5-6.1 3-3 5.5-5.8 5.5-6.2 0-.4-1.5-.8-3.3-.8-2.8 0-4.4 1-9.6 6.5-3.5 3.6-6.5 6.5-6.7 6.5-.2 0-.4-2.9-.4-6.5V135h-3c-1.7 0-3.3.6-3.6 1.3zm31.2 7c-1.1.8-1.5 1.9-1 3 .5 1.4 1.3 1.6 4 1.1 4.2-.8 8.4.2 8.4 2 0 .8-1.8 1.5-5.1 1.9-6 .7-8.9 2.9-8.9 6.6 0 3.2.8 4.4 3.7 6 2.9 1.5 5.2 1.4 8.6-.3 2.3-1.3 2.7-1.3 2.7 0 0 .9 1.1 1.4 3 1.4h3v-8.6c0-8.1-.1-8.7-2.9-11.5-2.5-2.5-3.7-2.9-8.3-2.9-3 0-6.2.6-7.2 1.3zm11.2 13.9c-.2 1.7-1.1 2.4-3.2 2.6-3.3.4-5.1-1-4.3-3.2.4-1.1 1.9-1.6 4.2-1.6 3.2 0 3.6.3 3.3 2.2zm13.4-4l.3 11.3h6l.5-7.8c.5-7.6 1.5-9.6 4.7-9.7 3 0 4.3 3.2 4.3 10.6v7.4h3c3 0 3 0 3-5.9 0-7.3 1.2-10.7 4.1-11.6 3.8-1.3 5.9 2.5 5.9 10.6v6.9h6v-9c0-8.3-.2-9.3-2.5-11.5-2.9-3-9.8-3.5-12.7-.8-1.7 1.5-1.9 1.5-3.6 0-2.2-2-9.2-2.3-11.1-.5-1.1 1-1.4 1-1.8 0-.3-.6-1.8-1.2-3.4-1.2h-3l.3 11.2zm45.4-9.9c-1.1.8-1.5 1.9-1 3 .5 1.4 1.3 1.6 4 1.1 4.2-.8 8.4.2 8.4 2 0 .8-1.8 1.5-5.1 1.9-6 .7-8.9 2.9-8.9 6.6 0 3.2.8 4.4 3.7 6 2.9 1.5 5.2 1.4 8.6-.3 2.3-1.3 2.7-1.3 2.7 0 0 .9 1.1 1.4 3 1.4h3v-8.6c0-8.1-.1-8.7-2.9-11.5-2.5-2.5-3.7-2.9-8.3-2.9-3 0-6.2.6-7.2 1.3zm11.2 13.9c-.2 1.7-1.1 2.4-3.2 2.6-3.3.4-5.1-1-4.3-3.2.4-1.1 1.9-1.6 4.2-1.6 3.2 0 3.6.3 3.3 2.2zm13-2.5c-.3 12.8-.3 12.8-2.7 12.8-1.5 0-2.7.8-3.1 2-2 5.4 9.4 4.3 11.9-1.2.6-1.3 1.1-7.7 1.1-14.3v-12h-6.9l-.3 12.7zm13.4-1.5l.3 11.3h6v-22l-3.3-.3-3.3-.3.3 11.3z"/></svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
@@ -1,37 +1,26 @@
|
||||
apiVersion: v2
|
||||
name: kamaji
|
||||
description: Kamaji is a tool aimed to build and operate a Managed Kubernetes Service with a fraction of the operational burden. With Kamaji, you can deploy and operate hundreds of Kubernetes clusters as a hyper-scaler.
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
#
|
||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||
# to be deployed.
|
||||
#
|
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.3.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: 0.1.0
|
||||
|
||||
appVersion: v0.1.1
|
||||
description: Kamaji is a tool aimed to build and operate a Managed Kubernetes Service
|
||||
with a fraction of the operational burden. With Kamaji, you can deploy and operate
|
||||
hundreds of Kubernetes clusters as a hyper-scaler.
|
||||
home: https://github.com/clastix/kamaji
|
||||
sources: ["https://github.com/clastix/kamaji"]
|
||||
kubeVersion: ">=1.18"
|
||||
icon: https://github.com/clastix/kamaji/raw/master/assets/kamaji-logo.png
|
||||
kubeVersion: 1.21 - 1.25
|
||||
maintainers:
|
||||
- email: iam@mendrugory.com
|
||||
name: Gonzalo Gabriel Jiménez Fuentes
|
||||
- email: dario@tranchitella.eu
|
||||
name: Dario Tranchitella
|
||||
- email: me@maxgio.it
|
||||
name: Massimiliano Giovagnoli
|
||||
- email: me@bsctl.io
|
||||
name: Adriano Pezzuto
|
||||
- email: iam@mendrugory.com
|
||||
name: Gonzalo Gabriel Jiménez Fuentes
|
||||
name: kamaji
|
||||
sources:
|
||||
- https://github.com/clastix/kamaji
|
||||
type: application
|
||||
version: 0.10.0
|
||||
annotations:
|
||||
catalog.cattle.io/certified: partner
|
||||
catalog.cattle.io/release-name: kamaji
|
||||
catalog.cattle.io/display-name: Kamaji - Managed Kubernetes Service
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# kamaji
|
||||
|
||||
  
|
||||
  
|
||||
|
||||
Kamaji is a tool aimed to build and operate a Managed Kubernetes Service with a fraction of the operational burden. With Kamaji, you can deploy and operate hundreds of Kubernetes clusters as a hyper-scaler.
|
||||
|
||||
@@ -8,10 +8,10 @@ Kamaji is a tool aimed to build and operate a Managed Kubernetes Service with a
|
||||
|
||||
| Name | Email | Url |
|
||||
| ---- | ------ | --- |
|
||||
| Gonzalo Gabriel Jiménez Fuentes | <iam@mendrugory.com> | |
|
||||
| Dario Tranchitella | <dario@tranchitella.eu> | |
|
||||
| Massimiliano Giovagnoli | <me@maxgio.it> | |
|
||||
| Adriano Pezzuto | <me@bsctl.io> | |
|
||||
| Gonzalo Gabriel Jiménez Fuentes | <iam@mendrugory.com> | |
|
||||
|
||||
## Source Code
|
||||
|
||||
@@ -19,7 +19,7 @@ Kamaji is a tool aimed to build and operate a Managed Kubernetes Service with a
|
||||
|
||||
## Requirements
|
||||
|
||||
Kubernetes: `>=1.18`
|
||||
Kubernetes: `1.21 - 1.25`
|
||||
|
||||
[Kamaji](https://github.com/clastix/kamaji) requires a [multi-tenant `etcd`](https://github.com/clastix/kamaji-internal/blob/master/deploy/getting-started-with-kamaji.md#setup-internal-multi-tenant-etcd) cluster.
|
||||
This Helm Chart starting from v0.1.1 provides the installation of an internal `etcd` in order to streamline the local test. If you'd like to use an externally managed etcd instance, you can specify the overrides and by setting the value `etcd.deploy=false`.
|
||||
@@ -97,7 +97,7 @@ Here the values you can override:
|
||||
| etcd.overrides.caSecret.namespace | string | `"kamaji-system"` | Namespace of the secret which contains CA's certificate and private key. (default: "kamaji-system") |
|
||||
| etcd.overrides.clientSecret.name | string | `"root-client-certs"` | Name of the secret which contains ETCD client certificates. (default: "root-client-certs") |
|
||||
| etcd.overrides.clientSecret.namespace | string | `"kamaji-system"` | Name of the namespace where the secret which contains ETCD client certificates is. (default: "kamaji-system") |
|
||||
| etcd.overrides.endpoints | object | `{"etcd-0":"https://etcd-0.etcd.kamaji-system.svc.cluster.local","etcd-1":"https://etcd-1.etcd.kamaji-system.svc.cluster.local","etcd-2":"https://etcd-2.etcd.kamaji-system.svc.cluster.local"}` | (map) Dictionary of the endpoints for the etcd cluster's members, key is the name of the etcd server. Don't define any port, inflected from .etcd.peerApiPort value. |
|
||||
| etcd.overrides.endpoints | object | `{"etcd-0":"etcd-0.etcd.kamaji-system.svc.cluster.local","etcd-1":"etcd-1.etcd.kamaji-system.svc.cluster.local","etcd-2":"etcd-2.etcd.kamaji-system.svc.cluster.local"}` | (map) Dictionary of the endpoints for the etcd cluster's members, key is the name of the etcd server. Don't define the protocol (TLS is automatically inflected), or any port, inflected from .etcd.peerApiPort value. |
|
||||
| etcd.peerApiPort | int | `2380` | The peer API port which servers are listening to. |
|
||||
| etcd.persistence.accessModes[0] | string | `"ReadWriteOnce"` | |
|
||||
| etcd.persistence.size | string | `"10Gi"` | |
|
||||
@@ -110,7 +110,7 @@ Here the values you can override:
|
||||
| healthProbeBindAddress | string | `":8081"` | The address the probe endpoint binds to. (default ":8081") |
|
||||
| image.pullPolicy | string | `"Always"` | |
|
||||
| image.repository | string | `"clastix/kamaji"` | The container image of the Kamaji controller. |
|
||||
| image.tag | string | `"latest"` | |
|
||||
| image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. |
|
||||
| imagePullSecrets | list | `[]` | |
|
||||
| livenessProbe | object | `{"httpGet":{"path":"/healthz","port":"healthcheck"},"initialDelaySeconds":15,"periodSeconds":20}` | The livenessProbe for the controller container |
|
||||
| loggingDevel.enable | bool | `false` | (string) Development Mode defaults(encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn). Production Mode defaults(encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error) (default false) |
|
||||
|
||||
30
charts/kamaji/app-readme.md
Normal file
30
charts/kamaji/app-readme.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Kamaji - Managed Kubernetes Service
|
||||
|
||||
Kamaji is a tool aimed to build and operate a Managed Kubernetes Service with a fraction of the operational burden.
|
||||
|
||||
Useful links:
|
||||
- [Kamaji Github repository](https://github.com/clastix/kamaji)
|
||||
- [Kamaji Documentation](https://github.com/clastix/kamaji/docs/)
|
||||
|
||||
## Requirements
|
||||
|
||||
* Kubernetes v1.22+
|
||||
* Helm v3
|
||||
|
||||
# Installation
|
||||
|
||||
To install the Chart with the release name `kamaji`:
|
||||
|
||||
helm upgrade --install --namespace kamaji-system --create-namespace clastix/kamaji
|
||||
|
||||
Show the status:
|
||||
|
||||
helm status kamaji -n kamaji-system
|
||||
|
||||
Upgrade the Chart
|
||||
|
||||
helm upgrade kamaji -n kamaji-system clastix/kamaji
|
||||
|
||||
Uninstall the Chart
|
||||
|
||||
helm uninstall kamaji -n kamaji-system
|
||||
@@ -1,10 +1,9 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.6.1
|
||||
controller-gen.kubebuilder.io/version: v0.9.2
|
||||
creationTimestamp: null
|
||||
name: datastores.kamaji.clastix.io
|
||||
spec:
|
||||
@@ -63,16 +62,17 @@ spec:
|
||||
where the content is stored. This value is mandatory.
|
||||
type: string
|
||||
name:
|
||||
description: Name is unique within a namespace to reference
|
||||
description: name is unique within a namespace to reference
|
||||
a secret resource.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace defines the space within which
|
||||
description: namespace defines the space within which
|
||||
the secret name must be unique.
|
||||
type: string
|
||||
required:
|
||||
- keyPath
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
username:
|
||||
properties:
|
||||
@@ -88,16 +88,17 @@ spec:
|
||||
where the content is stored. This value is mandatory.
|
||||
type: string
|
||||
name:
|
||||
description: Name is unique within a namespace to reference
|
||||
description: name is unique within a namespace to reference
|
||||
a secret resource.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace defines the space within which
|
||||
description: namespace defines the space within which
|
||||
the secret name must be unique.
|
||||
type: string
|
||||
required:
|
||||
- keyPath
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
required:
|
||||
- password
|
||||
@@ -137,16 +138,17 @@ spec:
|
||||
is mandatory.
|
||||
type: string
|
||||
name:
|
||||
description: Name is unique within a namespace to
|
||||
description: name is unique within a namespace to
|
||||
reference a secret resource.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace defines the space within which
|
||||
description: namespace defines the space within which
|
||||
the secret name must be unique.
|
||||
type: string
|
||||
required:
|
||||
- keyPath
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
privateKey:
|
||||
properties:
|
||||
@@ -163,16 +165,17 @@ spec:
|
||||
is mandatory.
|
||||
type: string
|
||||
name:
|
||||
description: Name is unique within a namespace to
|
||||
description: name is unique within a namespace to
|
||||
reference a secret resource.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace defines the space within which
|
||||
description: namespace defines the space within which
|
||||
the secret name must be unique.
|
||||
type: string
|
||||
required:
|
||||
- keyPath
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
required:
|
||||
- certificate
|
||||
@@ -196,16 +199,17 @@ spec:
|
||||
is mandatory.
|
||||
type: string
|
||||
name:
|
||||
description: Name is unique within a namespace to
|
||||
description: name is unique within a namespace to
|
||||
reference a secret resource.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace defines the space within which
|
||||
description: namespace defines the space within which
|
||||
the secret name must be unique.
|
||||
type: string
|
||||
required:
|
||||
- keyPath
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
privateKey:
|
||||
properties:
|
||||
@@ -222,16 +226,17 @@ spec:
|
||||
is mandatory.
|
||||
type: string
|
||||
name:
|
||||
description: Name is unique within a namespace to
|
||||
description: name is unique within a namespace to
|
||||
reference a secret resource.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace defines the space within which
|
||||
description: namespace defines the space within which
|
||||
the secret name must be unique.
|
||||
type: string
|
||||
required:
|
||||
- keyPath
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
required:
|
||||
- certificate
|
||||
@@ -261,9 +266,3 @@ spec:
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,8 +9,8 @@ image:
|
||||
# -- The container image of the Kamaji controller.
|
||||
repository: clastix/kamaji
|
||||
pullPolicy: Always
|
||||
# Overrides the image tag whose default is the chart appVersion.
|
||||
tag: latest
|
||||
# -- Overrides the image tag whose default is the chart appVersion.
|
||||
tag:
|
||||
|
||||
# -- A list of extra arguments to add to the kamaji controller default ones
|
||||
extraArgs: []
|
||||
@@ -67,11 +67,11 @@ etcd:
|
||||
name: root-client-certs
|
||||
# -- Name of the namespace where the secret which contains ETCD client certificates is. (default: "kamaji-system")
|
||||
namespace: kamaji-system
|
||||
# -- (map) Dictionary of the endpoints for the etcd cluster's members, key is the name of the etcd server. Don't define any port, inflected from .etcd.peerApiPort value.
|
||||
# -- (map) Dictionary of the endpoints for the etcd cluster's members, key is the name of the etcd server. Don't define the protocol (TLS is automatically inflected), or any port, inflected from .etcd.peerApiPort value.
|
||||
endpoints:
|
||||
etcd-0: https://etcd-0.etcd.kamaji-system.svc.cluster.local
|
||||
etcd-1: https://etcd-1.etcd.kamaji-system.svc.cluster.local
|
||||
etcd-2: https://etcd-2.etcd.kamaji-system.svc.cluster.local
|
||||
etcd-0: etcd-0.etcd.kamaji-system.svc.cluster.local
|
||||
etcd-1: etcd-1.etcd.kamaji-system.svc.cluster.local
|
||||
etcd-2: etcd-2.etcd.kamaji-system.svc.cluster.local
|
||||
# -- ETCD Compaction interval (e.g. "5m0s"). (default: "0" (disabled))
|
||||
compactionInterval: 0
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.6.1
|
||||
controller-gen.kubebuilder.io/version: v0.9.2
|
||||
creationTimestamp: null
|
||||
name: datastores.kamaji.clastix.io
|
||||
spec:
|
||||
@@ -63,16 +62,17 @@ spec:
|
||||
where the content is stored. This value is mandatory.
|
||||
type: string
|
||||
name:
|
||||
description: Name is unique within a namespace to reference
|
||||
description: name is unique within a namespace to reference
|
||||
a secret resource.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace defines the space within which
|
||||
description: namespace defines the space within which
|
||||
the secret name must be unique.
|
||||
type: string
|
||||
required:
|
||||
- keyPath
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
username:
|
||||
properties:
|
||||
@@ -88,16 +88,17 @@ spec:
|
||||
where the content is stored. This value is mandatory.
|
||||
type: string
|
||||
name:
|
||||
description: Name is unique within a namespace to reference
|
||||
description: name is unique within a namespace to reference
|
||||
a secret resource.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace defines the space within which
|
||||
description: namespace defines the space within which
|
||||
the secret name must be unique.
|
||||
type: string
|
||||
required:
|
||||
- keyPath
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
required:
|
||||
- password
|
||||
@@ -137,16 +138,17 @@ spec:
|
||||
is mandatory.
|
||||
type: string
|
||||
name:
|
||||
description: Name is unique within a namespace to
|
||||
description: name is unique within a namespace to
|
||||
reference a secret resource.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace defines the space within which
|
||||
description: namespace defines the space within which
|
||||
the secret name must be unique.
|
||||
type: string
|
||||
required:
|
||||
- keyPath
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
privateKey:
|
||||
properties:
|
||||
@@ -163,16 +165,17 @@ spec:
|
||||
is mandatory.
|
||||
type: string
|
||||
name:
|
||||
description: Name is unique within a namespace to
|
||||
description: name is unique within a namespace to
|
||||
reference a secret resource.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace defines the space within which
|
||||
description: namespace defines the space within which
|
||||
the secret name must be unique.
|
||||
type: string
|
||||
required:
|
||||
- keyPath
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
required:
|
||||
- certificate
|
||||
@@ -196,16 +199,17 @@ spec:
|
||||
is mandatory.
|
||||
type: string
|
||||
name:
|
||||
description: Name is unique within a namespace to
|
||||
description: name is unique within a namespace to
|
||||
reference a secret resource.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace defines the space within which
|
||||
description: namespace defines the space within which
|
||||
the secret name must be unique.
|
||||
type: string
|
||||
required:
|
||||
- keyPath
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
privateKey:
|
||||
properties:
|
||||
@@ -222,16 +226,17 @@ spec:
|
||||
is mandatory.
|
||||
type: string
|
||||
name:
|
||||
description: Name is unique within a namespace to
|
||||
description: name is unique within a namespace to
|
||||
reference a secret resource.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace defines the space within which
|
||||
description: namespace defines the space within which
|
||||
the secret name must be unique.
|
||||
type: string
|
||||
required:
|
||||
- keyPath
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
required:
|
||||
- certificate
|
||||
@@ -261,9 +266,3 @@ spec:
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@
|
||||
# It should be run by config/default
|
||||
resources:
|
||||
- bases/kamaji.clastix.io_tenantcontrolplanes.yaml
|
||||
- bases/kamaji.clastix.io_datastores.yaml
|
||||
#+kubebuilder:scaffold:crdkustomizeresource
|
||||
|
||||
patchesStrategicMerge:
|
||||
|
||||
@@ -25,3 +25,4 @@ spec:
|
||||
- "--health-probe-bind-address=:8081"
|
||||
- "--metrics-bind-address=127.0.0.1:8080"
|
||||
- "--leader-elect"
|
||||
- "--datastore=kamaji-etcd"
|
||||
|
||||
@@ -9,7 +9,235 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.6.1
|
||||
controller-gen.kubebuilder.io/version: v0.9.2
|
||||
creationTimestamp: null
|
||||
name: datastores.kamaji.clastix.io
|
||||
spec:
|
||||
group: kamaji.clastix.io
|
||||
names:
|
||||
kind: DataStore
|
||||
listKind: DataStoreList
|
||||
plural: datastores
|
||||
singular: datastore
|
||||
scope: Cluster
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- description: Kamaji data store driver
|
||||
jsonPath: .spec.driver
|
||||
name: Driver
|
||||
type: string
|
||||
- description: Age
|
||||
jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: DataStore is the Schema for the datastores API.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: DataStoreSpec defines the desired state of DataStore.
|
||||
properties:
|
||||
basicAuth:
|
||||
description: In case of authentication enabled for the given data store, specifies the username and password pair. This value is optional.
|
||||
properties:
|
||||
password:
|
||||
properties:
|
||||
content:
|
||||
description: Bare content of the file, base64 encoded. It has precedence over the SecretReference value.
|
||||
format: byte
|
||||
type: string
|
||||
secretReference:
|
||||
properties:
|
||||
keyPath:
|
||||
description: Name of the key for the given Secret reference where the content is stored. This value is mandatory.
|
||||
type: string
|
||||
name:
|
||||
description: name is unique within a namespace to reference a secret resource.
|
||||
type: string
|
||||
namespace:
|
||||
description: namespace defines the space within which the secret name must be unique.
|
||||
type: string
|
||||
required:
|
||||
- keyPath
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
username:
|
||||
properties:
|
||||
content:
|
||||
description: Bare content of the file, base64 encoded. It has precedence over the SecretReference value.
|
||||
format: byte
|
||||
type: string
|
||||
secretReference:
|
||||
properties:
|
||||
keyPath:
|
||||
description: Name of the key for the given Secret reference where the content is stored. This value is mandatory.
|
||||
type: string
|
||||
name:
|
||||
description: name is unique within a namespace to reference a secret resource.
|
||||
type: string
|
||||
namespace:
|
||||
description: namespace defines the space within which the secret name must be unique.
|
||||
type: string
|
||||
required:
|
||||
- keyPath
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
required:
|
||||
- password
|
||||
- username
|
||||
type: object
|
||||
driver:
|
||||
description: The driver to use to connect to the shared datastore.
|
||||
type: string
|
||||
endpoints:
|
||||
description: List of the endpoints to connect to the shared datastore. No need for protocol, just bare IP/FQDN and port.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
tlsConfig:
|
||||
description: Defines the TLS/SSL configuration required to connect to the data store in a secure way.
|
||||
properties:
|
||||
certificateAuthority:
|
||||
description: Retrieve the Certificate Authority certificate and private key, such as bare content of the file, or a SecretReference. The key reference is required since etcd authentication is based on certificates, and Kamaji is responsible in creating this.
|
||||
properties:
|
||||
certificate:
|
||||
properties:
|
||||
content:
|
||||
description: Bare content of the file, base64 encoded. It has precedence over the SecretReference value.
|
||||
format: byte
|
||||
type: string
|
||||
secretReference:
|
||||
properties:
|
||||
keyPath:
|
||||
description: Name of the key for the given Secret reference where the content is stored. This value is mandatory.
|
||||
type: string
|
||||
name:
|
||||
description: name is unique within a namespace to reference a secret resource.
|
||||
type: string
|
||||
namespace:
|
||||
description: namespace defines the space within which the secret name must be unique.
|
||||
type: string
|
||||
required:
|
||||
- keyPath
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
privateKey:
|
||||
properties:
|
||||
content:
|
||||
description: Bare content of the file, base64 encoded. It has precedence over the SecretReference value.
|
||||
format: byte
|
||||
type: string
|
||||
secretReference:
|
||||
properties:
|
||||
keyPath:
|
||||
description: Name of the key for the given Secret reference where the content is stored. This value is mandatory.
|
||||
type: string
|
||||
name:
|
||||
description: name is unique within a namespace to reference a secret resource.
|
||||
type: string
|
||||
namespace:
|
||||
description: namespace defines the space within which the secret name must be unique.
|
||||
type: string
|
||||
required:
|
||||
- keyPath
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
required:
|
||||
- certificate
|
||||
type: object
|
||||
clientCertificate:
|
||||
description: Specifies the SSL/TLS key and private key pair used to connect to the data store.
|
||||
properties:
|
||||
certificate:
|
||||
properties:
|
||||
content:
|
||||
description: Bare content of the file, base64 encoded. It has precedence over the SecretReference value.
|
||||
format: byte
|
||||
type: string
|
||||
secretReference:
|
||||
properties:
|
||||
keyPath:
|
||||
description: Name of the key for the given Secret reference where the content is stored. This value is mandatory.
|
||||
type: string
|
||||
name:
|
||||
description: name is unique within a namespace to reference a secret resource.
|
||||
type: string
|
||||
namespace:
|
||||
description: namespace defines the space within which the secret name must be unique.
|
||||
type: string
|
||||
required:
|
||||
- keyPath
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
privateKey:
|
||||
properties:
|
||||
content:
|
||||
description: Bare content of the file, base64 encoded. It has precedence over the SecretReference value.
|
||||
format: byte
|
||||
type: string
|
||||
secretReference:
|
||||
properties:
|
||||
keyPath:
|
||||
description: Name of the key for the given Secret reference where the content is stored. This value is mandatory.
|
||||
type: string
|
||||
name:
|
||||
description: name is unique within a namespace to reference a secret resource.
|
||||
type: string
|
||||
namespace:
|
||||
description: namespace defines the space within which the secret name must be unique.
|
||||
type: string
|
||||
required:
|
||||
- keyPath
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
required:
|
||||
- certificate
|
||||
- privateKey
|
||||
type: object
|
||||
required:
|
||||
- certificateAuthority
|
||||
- clientCertificate
|
||||
type: object
|
||||
required:
|
||||
- driver
|
||||
- endpoints
|
||||
- tlsConfig
|
||||
type: object
|
||||
status:
|
||||
description: DataStoreStatus defines the observed state of DataStore.
|
||||
properties:
|
||||
usedBy:
|
||||
description: List of the Tenant Control Planes, namespaced named, using this data store.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.9.2
|
||||
creationTimestamp: null
|
||||
name: tenantcontrolplanes.kamaji.clastix.io
|
||||
spec:
|
||||
@@ -64,13 +292,20 @@ spec:
|
||||
description: Addons contain which addons are enabled
|
||||
properties:
|
||||
coreDNS:
|
||||
description: AddonSpec defines the spec for every addon.
|
||||
description: Enables the DNS addon in the Tenant Cluster. The registry and the tag are configurable, the image is hard-coded to `coredns`.
|
||||
properties:
|
||||
imageRepository:
|
||||
description: ImageRepository sets the container registry to pull images from. if not set, the default ImageRepository will be used instead.
|
||||
type: string
|
||||
imageTag:
|
||||
description: ImageTag allows to specify a tag for the image. In case this value is set, kubeadm does not change automatically the version of the above components during upgrades.
|
||||
type: string
|
||||
type: object
|
||||
konnectivity:
|
||||
description: KonnectivitySpec defines the spec for Konnectivity.
|
||||
description: Enables the Konnectivity addon in the Tenant Cluster, required if the worker nodes are in a different network.
|
||||
properties:
|
||||
agentImage:
|
||||
default: us.gcr.io/k8s-artifacts-prod/kas-network-proxy/proxy-agent
|
||||
default: registry.k8s.io/kas-network-proxy/proxy-agent
|
||||
description: AgentImage defines the container image for Konnectivity's agent.
|
||||
type: string
|
||||
proxyPort:
|
||||
@@ -100,18 +335,25 @@ spec:
|
||||
type: object
|
||||
type: object
|
||||
serverImage:
|
||||
default: us.gcr.io/k8s-artifacts-prod/kas-network-proxy/proxy-server
|
||||
default: registry.k8s.io/kas-network-proxy/proxy-server
|
||||
description: ServerImage defines the container image for Konnectivity's server.
|
||||
type: string
|
||||
version:
|
||||
default: v0.0.31
|
||||
default: v0.0.32
|
||||
description: Version for Konnectivity server and agent.
|
||||
type: string
|
||||
required:
|
||||
- proxyPort
|
||||
type: object
|
||||
kubeProxy:
|
||||
description: AddonSpec defines the spec for every addon.
|
||||
description: Enables the kube-proxy addon in the Tenant Cluster. The registry and the tag are configurable, the image is hard-coded to `kube-proxy`.
|
||||
properties:
|
||||
imageRepository:
|
||||
description: ImageRepository sets the container registry to pull images from. if not set, the default ImageRepository will be used instead.
|
||||
type: string
|
||||
imageTag:
|
||||
description: ImageTag allows to specify a tag for the image. In case this value is set, kubeadm does not change automatically the version of the above components during upgrades.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
controlPlane:
|
||||
@@ -132,6 +374,478 @@ spec:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
affinity:
|
||||
description: 'If specified, the Tenant Control Plane pod''s scheduling constraints. More info: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/'
|
||||
properties:
|
||||
nodeAffinity:
|
||||
description: Describes node affinity scheduling rules for the pod.
|
||||
properties:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred.
|
||||
items:
|
||||
description: An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).
|
||||
properties:
|
||||
preference:
|
||||
description: A node selector term, associated with the corresponding weight.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: A list of node selector requirements by node's labels.
|
||||
items:
|
||||
description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: The label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.
|
||||
type: string
|
||||
values:
|
||||
description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchFields:
|
||||
description: A list of node selector requirements by node's fields.
|
||||
items:
|
||||
description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: The label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.
|
||||
type: string
|
||||
values:
|
||||
description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
weight:
|
||||
description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100.
|
||||
format: int32
|
||||
type: integer
|
||||
required:
|
||||
- preference
|
||||
- weight
|
||||
type: object
|
||||
type: array
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node.
|
||||
properties:
|
||||
nodeSelectorTerms:
|
||||
description: Required. A list of node selector terms. The terms are ORed.
|
||||
items:
|
||||
description: A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: A list of node selector requirements by node's labels.
|
||||
items:
|
||||
description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: The label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.
|
||||
type: string
|
||||
values:
|
||||
description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchFields:
|
||||
description: A list of node selector requirements by node's fields.
|
||||
items:
|
||||
description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: The label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.
|
||||
type: string
|
||||
values:
|
||||
description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: array
|
||||
required:
|
||||
- nodeSelectorTerms
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
podAffinity:
|
||||
description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)).
|
||||
properties:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.
|
||||
items:
|
||||
description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s)
|
||||
properties:
|
||||
podAffinityTerm:
|
||||
description: Required. A pod affinity term, associated with the corresponding weight.
|
||||
properties:
|
||||
labelSelector:
|
||||
description: A label query over a set of resources, in this case pods.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
namespaceSelector:
|
||||
description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
namespaces:
|
||||
description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace".
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
topologyKey:
|
||||
description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.
|
||||
type: string
|
||||
required:
|
||||
- topologyKey
|
||||
type: object
|
||||
weight:
|
||||
description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100.
|
||||
format: int32
|
||||
type: integer
|
||||
required:
|
||||
- podAffinityTerm
|
||||
- weight
|
||||
type: object
|
||||
type: array
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied.
|
||||
items:
|
||||
description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key <topologyKey> matches that of any node on which a pod of the set of pods is running
|
||||
properties:
|
||||
labelSelector:
|
||||
description: A label query over a set of resources, in this case pods.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
namespaceSelector:
|
||||
description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
namespaces:
|
||||
description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace".
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
topologyKey:
|
||||
description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.
|
||||
type: string
|
||||
required:
|
||||
- topologyKey
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
podAntiAffinity:
|
||||
description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)).
|
||||
properties:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
description: The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.
|
||||
items:
|
||||
description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s)
|
||||
properties:
|
||||
podAffinityTerm:
|
||||
description: Required. A pod affinity term, associated with the corresponding weight.
|
||||
properties:
|
||||
labelSelector:
|
||||
description: A label query over a set of resources, in this case pods.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
namespaceSelector:
|
||||
description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
namespaces:
|
||||
description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace".
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
topologyKey:
|
||||
description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.
|
||||
type: string
|
||||
required:
|
||||
- topologyKey
|
||||
type: object
|
||||
weight:
|
||||
description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100.
|
||||
format: int32
|
||||
type: integer
|
||||
required:
|
||||
- podAffinityTerm
|
||||
- weight
|
||||
type: object
|
||||
type: array
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
description: If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied.
|
||||
items:
|
||||
description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key <topologyKey> matches that of any node on which a pod of the set of pods is running
|
||||
properties:
|
||||
labelSelector:
|
||||
description: A label query over a set of resources, in this case pods.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
namespaceSelector:
|
||||
description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
namespaces:
|
||||
description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace".
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
topologyKey:
|
||||
description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.
|
||||
type: string
|
||||
required:
|
||||
- topologyKey
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
type: object
|
||||
extraArgs:
|
||||
description: ExtraArgs allows adding additional arguments to the Control Plane components, such as kube-apiserver, controller-manager, and scheduler.
|
||||
properties:
|
||||
@@ -153,6 +867,11 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
nodeSelector:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: 'NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node''s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/'
|
||||
type: object
|
||||
replicas:
|
||||
default: 2
|
||||
format: int32
|
||||
@@ -227,6 +946,97 @@ spec:
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
tolerations:
|
||||
description: 'If specified, the Tenant Control Plane pod''s tolerations. More info: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/'
|
||||
items:
|
||||
description: The pod this Toleration is attached to tolerates any taint that matches the triple <key,value,effect> using the matching operator <operator>.
|
||||
properties:
|
||||
effect:
|
||||
description: Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.
|
||||
type: string
|
||||
key:
|
||||
description: Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys.
|
||||
type: string
|
||||
operator:
|
||||
description: Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category.
|
||||
type: string
|
||||
tolerationSeconds:
|
||||
description: TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system.
|
||||
format: int64
|
||||
type: integer
|
||||
value:
|
||||
description: Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
topologySpreadConstraints:
|
||||
description: TopologySpreadConstraints describes how the Tenant Control Plane pods ought to spread across topology domains. Scheduler will schedule pods in a way which abides by the constraints. In case of nil underlying LabelSelector, the Kamaji one for the given Tenant Control Plane will be used. All topologySpreadConstraints are ANDed.
|
||||
items:
|
||||
description: TopologySpreadConstraint specifies how to spread matching pods among the given topology.
|
||||
properties:
|
||||
labelSelector:
|
||||
description: LabelSelector is used to find matching pods. Pods that match this label selector are counted to determine the number of pods in their corresponding topology domain.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
matchLabelKeys:
|
||||
description: MatchLabelKeys is a set of pod label keys to select the pods over which spreading will be calculated. The keys are used to lookup values from the incoming pod labels, those key-value labels are ANDed with labelSelector to select the group of existing pods over which spreading will be calculated for the incoming pod. Keys that don't exist in the incoming pod labels will be ignored. A null or empty list means only match against labelSelector.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
maxSkew:
|
||||
description: 'MaxSkew describes the degree to which pods may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference between the number of matching pods in the target topology and the global minimum. The global minimum is the minimum number of matching pods in an eligible domain or zero if the number of eligible domains is less than MinDomains. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 2/2/1: In this case, the global minimum is 1. | zone1 | zone2 | zone3 | | P P | P P | P | - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) violate MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled onto any zone. When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence to topologies that satisfy it. It''s a required field. Default value is 1 and 0 is not allowed.'
|
||||
format: int32
|
||||
type: integer
|
||||
minDomains:
|
||||
description: "MinDomains indicates a minimum number of eligible domains. When the number of eligible domains with matching topology keys is less than minDomains, Pod Topology Spread treats \"global minimum\" as 0, and then the calculation of Skew is performed. And when the number of eligible domains with matching topology keys equals or greater than minDomains, this value has no effect on scheduling. As a result, when the number of eligible domains is less than minDomains, scheduler won't schedule more than maxSkew Pods to those domains. If value is nil, the constraint behaves as if MinDomains is equal to 1. Valid values are integers greater than 0. When value is not nil, WhenUnsatisfiable must be DoNotSchedule. \n For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same labelSelector spread as 2/2/2: | zone1 | zone2 | zone3 | | P P | P P | P P | The number of domains is less than 5(MinDomains), so \"global minimum\" is treated as 0. In this situation, new pod with the same labelSelector cannot be scheduled, because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, it will violate MaxSkew. \n This is a beta field and requires the MinDomainsInPodTopologySpread feature gate to be enabled (enabled by default)."
|
||||
format: int32
|
||||
type: integer
|
||||
nodeAffinityPolicy:
|
||||
description: "NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector when calculating pod topology spread skew. Options are: - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. \n If this value is nil, the behavior is equivalent to the Honor policy. This is a alpha-level feature enabled by the NodeInclusionPolicyInPodTopologySpread feature flag."
|
||||
type: string
|
||||
nodeTaintsPolicy:
|
||||
description: "NodeTaintsPolicy indicates how we will treat node taints when calculating pod topology spread skew. Options are: - Honor: nodes without taints, along with tainted nodes for which the incoming pod has a toleration, are included. - Ignore: node taints are ignored. All nodes are included. \n If this value is nil, the behavior is equivalent to the Ignore policy. This is a alpha-level feature enabled by the NodeInclusionPolicyInPodTopologySpread feature flag."
|
||||
type: string
|
||||
topologyKey:
|
||||
description: TopologyKey is the key of node labels. Nodes that have a label with this key and identical values are considered to be in the same topology. We consider each <key, value> as a "bucket", and try to put balanced number of pods into each bucket. We define a domain as a particular instance of a topology. Also, we define an eligible domain as a domain whose nodes meet the requirements of nodeAffinityPolicy and nodeTaintsPolicy. e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. It's a required field.
|
||||
type: string
|
||||
whenUnsatisfiable:
|
||||
description: 'WhenUnsatisfiable indicates how to deal with a pod if it doesn''t satisfy the spread constraint. - DoNotSchedule (default) tells the scheduler not to schedule it. - ScheduleAnyway tells the scheduler to schedule the pod in any location, but giving higher precedence to topologies that would help reduce the skew. A constraint is considered "Unsatisfiable" for an incoming pod if and only if every possible node assignment for that pod would violate "MaxSkew" on some topology. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 3/1/1: | zone1 | zone2 | zone3 | | P P P | P | P | If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler won''t make it *more* imbalanced. It''s a required field.'
|
||||
type: string
|
||||
required:
|
||||
- maxSkew
|
||||
- topologyKey
|
||||
- whenUnsatisfiable
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
ingress:
|
||||
description: Defining the options for an Optional Ingress which will expose API Server of the Tenant Control Plane
|
||||
@@ -277,6 +1087,9 @@ spec:
|
||||
required:
|
||||
- service
|
||||
type: object
|
||||
dataStore:
|
||||
description: DataStore allows to specify a DataStore that should be used to store the Kubernetes data for the given Tenant Control Plane. This parameter is optional and acts as an override over the default one which is used by the Kamaji Operator. Migration from a different DataStore to another one is not yet supported and the reconciliation will be blocked.
|
||||
type: string
|
||||
kubernetes:
|
||||
description: Kubernetes specification for tenant control plane
|
||||
properties:
|
||||
@@ -493,7 +1306,7 @@ spec:
|
||||
conditions:
|
||||
description: Current service state
|
||||
items:
|
||||
description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
|
||||
description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||
@@ -556,7 +1369,7 @@ spec:
|
||||
items:
|
||||
properties:
|
||||
error:
|
||||
description: 'Error is to record the problem with the service port The format of the error shall comply with the following rules: - built-in error values shall be specified in this file and those shall use CamelCase names - cloud provider specific error values must have names that comply with the format foo.example.com/CamelCase. --- The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)'
|
||||
description: 'Error is to record the problem with the service port The format of the error shall comply with the following rules: - built-in error values shall be specified in this file and those shall use CamelCase names - cloud provider specific error values must have names that comply with the format foo.example.com/CamelCase. --- The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)'
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
@@ -858,6 +1671,9 @@ spec:
|
||||
description: Total number of non-terminated pods targeted by this deployment (their labels match the selector).
|
||||
format: int32
|
||||
type: integer
|
||||
selector:
|
||||
description: Selector is the label selector used to group the Tenant Control Plane Pods used by the scale subresource.
|
||||
type: string
|
||||
unavailableReplicas:
|
||||
description: Total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created.
|
||||
format: int32
|
||||
@@ -869,6 +1685,7 @@ spec:
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
- selector
|
||||
type: object
|
||||
ingress:
|
||||
description: KubernetesIngressStatus defines the status for the Tenant Control Plane Ingress in the management cluster.
|
||||
@@ -892,7 +1709,7 @@ spec:
|
||||
items:
|
||||
properties:
|
||||
error:
|
||||
description: 'Error is to record the problem with the service port The format of the error shall comply with the following rules: - built-in error values shall be specified in this file and those shall use CamelCase names - cloud provider specific error values must have names that comply with the format foo.example.com/CamelCase. --- The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)'
|
||||
description: 'Error is to record the problem with the service port The format of the error shall comply with the following rules: - built-in error values shall be specified in this file and those shall use CamelCase names - cloud provider specific error values must have names that comply with the format foo.example.com/CamelCase. --- The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)'
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
@@ -929,7 +1746,7 @@ spec:
|
||||
conditions:
|
||||
description: Current service state
|
||||
items:
|
||||
description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
|
||||
description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||
@@ -992,7 +1809,7 @@ spec:
|
||||
items:
|
||||
properties:
|
||||
error:
|
||||
description: 'Error is to record the problem with the service port The format of the error shall comply with the following rules: - built-in error values shall be specified in this file and those shall use CamelCase names - cloud provider specific error values must have names that comply with the format foo.example.com/CamelCase. --- The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)'
|
||||
description: 'Error is to record the problem with the service port The format of the error shall comply with the following rules: - built-in error values shall be specified in this file and those shall use CamelCase names - cloud provider specific error values must have names that comply with the format foo.example.com/CamelCase. --- The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)'
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
@@ -1065,6 +1882,8 @@ spec:
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
dataStoreName:
|
||||
type: string
|
||||
driver:
|
||||
type: string
|
||||
setup:
|
||||
@@ -1085,13 +1904,11 @@ spec:
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
scale:
|
||||
labelSelectorPath: .status.kubernetesResources.deployment.selector
|
||||
specReplicasPath: .spec.controlPlane.deployment.replicas
|
||||
statusReplicasPath: .status.kubernetesResources.deployment.replicas
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
@@ -1386,9 +2203,10 @@ spec:
|
||||
- --health-probe-bind-address=:8081
|
||||
- --metrics-bind-address=127.0.0.1:8080
|
||||
- --leader-elect
|
||||
- --datastore=kamaji-etcd
|
||||
command:
|
||||
- /manager
|
||||
image: clastix/kamaji:latest
|
||||
image: clastix/kamaji:v0.1.1
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -1426,9 +2244,9 @@ spec:
|
||||
basicAuth: null
|
||||
driver: etcd
|
||||
endpoints:
|
||||
- etcd-0.etcd.kamaji-system.svc:2379
|
||||
- etcd-1.etcd.kamaji-system.svc:2379
|
||||
- etcd-2.etcd.kamaji-system.svc:2379
|
||||
- etcd-0.etcd.kamaji-system.svc.cluster.local:2379
|
||||
- etcd-1.etcd.kamaji-system.svc.cluster.local:2379
|
||||
- etcd-2.etcd.kamaji-system.svc.cluster.local:2379
|
||||
tlsConfig:
|
||||
certificateAuthority:
|
||||
certificate:
|
||||
|
||||
@@ -13,4 +13,4 @@ kind: Kustomization
|
||||
images:
|
||||
- name: controller
|
||||
newName: clastix/kamaji
|
||||
newTag: latest
|
||||
newTag: v0.1.1
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
|
||||
@@ -5,9 +5,9 @@ metadata:
|
||||
spec:
|
||||
driver: etcd
|
||||
endpoints:
|
||||
- etcd-0.etcd.kamaji-system.svc:2379
|
||||
- etcd-1.etcd.kamaji-system.svc:2379
|
||||
- etcd-2.etcd.kamaji-system.svc:2379
|
||||
- etcd-0.etcd.kamaji-system.svc.cluster.local:2379
|
||||
- etcd-1.etcd.kamaji-system.svc.cluster.local:2379
|
||||
- etcd-2.etcd.kamaji-system.svc.cluster.local:2379
|
||||
basicAuth: null
|
||||
tlsConfig:
|
||||
certificateAuthority:
|
||||
|
||||
@@ -5,18 +5,30 @@ package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
controllerruntime "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/indexers"
|
||||
)
|
||||
|
||||
const (
|
||||
dataStoreFinalizer = "finalizer.kamaji.clastix.io/datastore"
|
||||
)
|
||||
|
||||
type DataStore struct {
|
||||
@@ -25,39 +37,87 @@ type DataStore struct {
|
||||
// if a Data Source is updated we have to be sure that the reconciliation of the certificates content
|
||||
// for each Tenant Control Plane is put in place properly.
|
||||
TenantControlPlaneTrigger TenantControlPlaneChannel
|
||||
// ResourceName is the DataStore object that should be watched for changes.
|
||||
ResourceName string
|
||||
}
|
||||
|
||||
//+kubebuilder:rbac:groups=kamaji.clastix.io,resources=datastores,verbs=get;list;watch;create;update;patch;delete
|
||||
//+kubebuilder:rbac:groups=kamaji.clastix.io,resources=datastores/status,verbs=get;update;patch
|
||||
|
||||
func (r *DataStore) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
|
||||
ds := kamajiv1alpha1.DataStore{}
|
||||
if err := r.client.Get(ctx, request.NamespacedName, &ds); err != nil {
|
||||
log := log.FromContext(ctx)
|
||||
|
||||
ds := &kamajiv1alpha1.DataStore{}
|
||||
if err := r.client.Get(ctx, request.NamespacedName, ds); err != nil {
|
||||
if k8serrors.IsNotFound(err) {
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
log.Error(err, "unable to retrieve the request")
|
||||
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
// Managing the finalizer, required to don't drop a DataSource if this is still used by a Tenant Control Plane.
|
||||
switch {
|
||||
case ds.DeletionTimestamp != nil && controllerutil.ContainsFinalizer(ds, dataStoreFinalizer):
|
||||
log.Info("marked for deletion, checking conditions")
|
||||
|
||||
if len(ds.Status.UsedBy) == 0 {
|
||||
log.Info("resource is no more used by any Tenant Control Plane")
|
||||
|
||||
controllerutil.RemoveFinalizer(ds, dataStoreFinalizer)
|
||||
|
||||
return reconcile.Result{}, r.client.Update(ctx, ds)
|
||||
}
|
||||
|
||||
log.Info("DataStore is still used by some Tenant Control Planes, cannot be removed")
|
||||
case ds.DeletionTimestamp == nil && !controllerutil.ContainsFinalizer(ds, dataStoreFinalizer):
|
||||
log.Info("the resource is missing the required finalizer, adding it")
|
||||
|
||||
controllerutil.AddFinalizer(ds, dataStoreFinalizer)
|
||||
|
||||
return reconcile.Result{}, r.client.Update(ctx, ds)
|
||||
}
|
||||
// A Data Source can trigger several Tenant Control Planes and requires a minimum validation:
|
||||
// we have to ensure the data provided by the Data Source is valid and referencing an existing Secret object.
|
||||
if _, err := ds.Spec.TLSConfig.CertificateAuthority.Certificate.GetContent(ctx, r.client); err != nil {
|
||||
return reconcile.Result{}, errors.Wrap(err, "invalid Certificate Authority data")
|
||||
log.Error(err, "invalid Certificate Authority data")
|
||||
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
if ds.Spec.Driver == kamajiv1alpha1.EtcdDriver {
|
||||
if ds.Spec.TLSConfig.CertificateAuthority.PrivateKey == nil {
|
||||
err := fmt.Errorf("a valid private key is required for the etcd driver")
|
||||
|
||||
log.Error(err, "missing Certificate Authority private key data")
|
||||
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
if _, err := ds.Spec.TLSConfig.CertificateAuthority.PrivateKey.GetContent(ctx, r.client); err != nil {
|
||||
log.Error(err, "invalid Certificate Authority private key data")
|
||||
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := ds.Spec.TLSConfig.ClientCertificate.Certificate.GetContent(ctx, r.client); err != nil {
|
||||
return reconcile.Result{}, errors.Wrap(err, "invalid Client Certificate data")
|
||||
log.Error(err, "invalid Client Certificate data")
|
||||
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
if _, err := ds.Spec.TLSConfig.ClientCertificate.PrivateKey.GetContent(ctx, r.client); err != nil {
|
||||
return reconcile.Result{}, errors.Wrap(err, "invalid Client Certificate data")
|
||||
log.Error(err, "invalid Client Certificate private key data")
|
||||
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
tcpList := kamajiv1alpha1.TenantControlPlaneList{}
|
||||
|
||||
if err := r.client.List(ctx, &tcpList); err != nil {
|
||||
if err := r.client.List(ctx, &tcpList, client.MatchingFieldsSelector{
|
||||
Selector: fields.OneTermEqualSelector(indexers.TenantControlPlaneUsedDataStoreKey, ds.GetName()),
|
||||
}); err != nil {
|
||||
log.Error(err, "cannot retrieve list of the Tenant Control Plane using the following instance")
|
||||
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
// Updating the status with the list of Tenant Control Plane using the following Data Source
|
||||
@@ -68,7 +128,9 @@ func (r *DataStore) Reconcile(ctx context.Context, request reconcile.Request) (r
|
||||
|
||||
ds.Status.UsedBy = tcpSets.List()
|
||||
|
||||
if err := r.client.Status().Update(ctx, &ds); err != nil {
|
||||
if err := r.client.Status().Update(ctx, ds); err != nil {
|
||||
log.Error(err, "cannot update the status for the given instance")
|
||||
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
// Triggering the reconciliation of the Tenant Control Plane upon a Secret change
|
||||
@@ -88,9 +150,31 @@ func (r *DataStore) InjectClient(client client.Client) error {
|
||||
}
|
||||
|
||||
func (r *DataStore) SetupWithManager(mgr controllerruntime.Manager) error {
|
||||
enqueueFn := func(tcp *kamajiv1alpha1.TenantControlPlane, limitingInterface workqueue.RateLimitingInterface) {
|
||||
if dataStoreName := tcp.Status.Storage.DataStoreName; len(dataStoreName) > 0 {
|
||||
limitingInterface.AddRateLimited(reconcile.Request{
|
||||
NamespacedName: k8stypes.NamespacedName{
|
||||
Name: dataStoreName,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
//nolint:forcetypeassert
|
||||
return controllerruntime.NewControllerManagedBy(mgr).
|
||||
For(&kamajiv1alpha1.DataStore{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
|
||||
return object.GetName() == r.ResourceName
|
||||
}))).
|
||||
For(&kamajiv1alpha1.DataStore{}, builder.WithPredicates(
|
||||
predicate.ResourceVersionChangedPredicate{},
|
||||
)).
|
||||
Watches(&source.Kind{Type: &kamajiv1alpha1.TenantControlPlane{}}, handler.Funcs{
|
||||
CreateFunc: func(createEvent event.CreateEvent, limitingInterface workqueue.RateLimitingInterface) {
|
||||
enqueueFn(createEvent.Object.(*kamajiv1alpha1.TenantControlPlane), limitingInterface)
|
||||
},
|
||||
UpdateFunc: func(updateEvent event.UpdateEvent, limitingInterface workqueue.RateLimitingInterface) {
|
||||
enqueueFn(updateEvent.ObjectOld.(*kamajiv1alpha1.TenantControlPlane), limitingInterface)
|
||||
enqueueFn(updateEvent.ObjectNew.(*kamajiv1alpha1.TenantControlPlane), limitingInterface)
|
||||
},
|
||||
DeleteFunc: func(deleteEvent event.DeleteEvent, limitingInterface workqueue.RateLimitingInterface) {
|
||||
enqueueFn(deleteEvent.Object.(*kamajiv1alpha1.TenantControlPlane), limitingInterface)
|
||||
},
|
||||
}).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
@@ -52,14 +52,14 @@ func GetDeletableResources(config GroupDeleteableResourceBuilderConfiguration) [
|
||||
func getDefaultResources(config GroupResourceBuilderConfiguration) []resources.Resource {
|
||||
resources := append(getUpgradeResources(config.client), getKubernetesServiceResources(config.client)...)
|
||||
resources = append(resources, getKubeadmConfigResources(config.client, getTmpDirectory(config.tcpReconcilerConfig.TmpBaseDirectory, config.tenantControlPlane), config.DataStore)...)
|
||||
resources = append(resources, getKubernetesCertificatesResources(config.client, config.log, config.tcpReconcilerConfig, config.tenantControlPlane)...)
|
||||
resources = append(resources, getKubeconfigResources(config.client, config.log, config.tcpReconcilerConfig, config.tenantControlPlane)...)
|
||||
resources = append(resources, getKubernetesCertificatesResources(config.client, config.tcpReconcilerConfig, config.tenantControlPlane)...)
|
||||
resources = append(resources, getKubeconfigResources(config.client, config.tcpReconcilerConfig, config.tenantControlPlane)...)
|
||||
resources = append(resources, getKubernetesStorageResources(config.client, config.Connection, config.DataStore)...)
|
||||
resources = append(resources, getInternalKonnectivityResources(config.client, config.log)...)
|
||||
resources = append(resources, getInternalKonnectivityResources(config.client)...)
|
||||
resources = append(resources, getKubernetesDeploymentResources(config.client, config.tcpReconcilerConfig, config.DataStore)...)
|
||||
resources = append(resources, getKubernetesIngressResources(config.client)...)
|
||||
resources = append(resources, getKubeadmPhaseResources(config.client, config.log)...)
|
||||
resources = append(resources, getKubeadmAddonResources(config.client, config.log)...)
|
||||
resources = append(resources, getKubeadmPhaseResources(config.client)...)
|
||||
resources = append(resources, getKubeadmAddonResources(config.client)...)
|
||||
resources = append(resources, getExternalKonnectivityResources(config.client)...)
|
||||
|
||||
return resources
|
||||
@@ -109,61 +109,52 @@ func getKubeadmConfigResources(c client.Client, tmpDirectory string, dataStore k
|
||||
}
|
||||
}
|
||||
|
||||
func getKubernetesCertificatesResources(c client.Client, log logr.Logger, tcpReconcilerConfig TenantControlPlaneReconcilerConfig, tenantControlPlane kamajiv1alpha1.TenantControlPlane) []resources.Resource {
|
||||
func getKubernetesCertificatesResources(c client.Client, tcpReconcilerConfig TenantControlPlaneReconcilerConfig, tenantControlPlane kamajiv1alpha1.TenantControlPlane) []resources.Resource {
|
||||
return []resources.Resource{
|
||||
&resources.CACertificate{
|
||||
Client: c,
|
||||
Log: log,
|
||||
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
|
||||
},
|
||||
&resources.FrontProxyCACertificate{
|
||||
Client: c,
|
||||
Log: log,
|
||||
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
|
||||
},
|
||||
&resources.SACertificate{
|
||||
Client: c,
|
||||
Log: log,
|
||||
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
|
||||
},
|
||||
&resources.APIServerCertificate{
|
||||
Client: c,
|
||||
Log: log,
|
||||
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
|
||||
},
|
||||
&resources.APIServerKubeletClientCertificate{
|
||||
Client: c,
|
||||
Log: log,
|
||||
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
|
||||
},
|
||||
&resources.FrontProxyClientCertificate{
|
||||
Client: c,
|
||||
Log: log,
|
||||
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getKubeconfigResources(c client.Client, log logr.Logger, tcpReconcilerConfig TenantControlPlaneReconcilerConfig, tenantControlPlane kamajiv1alpha1.TenantControlPlane) []resources.Resource {
|
||||
func getKubeconfigResources(c client.Client, tcpReconcilerConfig TenantControlPlaneReconcilerConfig, tenantControlPlane kamajiv1alpha1.TenantControlPlane) []resources.Resource {
|
||||
return []resources.Resource{
|
||||
&resources.KubeconfigResource{
|
||||
Name: "admin-kubeconfig",
|
||||
Client: c,
|
||||
Log: log,
|
||||
KubeConfigFileName: resources.AdminKubeConfigFileName,
|
||||
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
|
||||
},
|
||||
&resources.KubeconfigResource{
|
||||
Name: "controller-manager-kubeconfig",
|
||||
Client: c,
|
||||
Log: log,
|
||||
KubeConfigFileName: resources.ControllerManagerKubeConfigFileName,
|
||||
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
|
||||
},
|
||||
&resources.KubeconfigResource{
|
||||
Name: "scheduler-kubeconfig",
|
||||
Client: c,
|
||||
Log: log,
|
||||
KubeConfigFileName: resources.SchedulerKubeConfigFileName,
|
||||
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
|
||||
},
|
||||
@@ -175,7 +166,7 @@ func getKubernetesStorageResources(c client.Client, dbConnection datastore.Conne
|
||||
&ds.Config{
|
||||
Client: c,
|
||||
ConnString: dbConnection.GetConnectionString(),
|
||||
Driver: dbConnection.Driver(),
|
||||
DataStore: datastore,
|
||||
},
|
||||
&ds.Setup{
|
||||
Client: c,
|
||||
@@ -207,41 +198,36 @@ func getKubernetesIngressResources(c client.Client) []resources.Resource {
|
||||
}
|
||||
}
|
||||
|
||||
func getKubeadmPhaseResources(c client.Client, log logr.Logger) []resources.Resource {
|
||||
func getKubeadmPhaseResources(c client.Client) []resources.Resource {
|
||||
return []resources.Resource{
|
||||
&resources.KubeadmPhase{
|
||||
Name: "upload-config-kubeadm",
|
||||
Client: c,
|
||||
Log: log,
|
||||
Phase: resources.PhaseUploadConfigKubeadm,
|
||||
},
|
||||
&resources.KubeadmPhase{
|
||||
Name: "upload-config-kubelet",
|
||||
Client: c,
|
||||
Log: log,
|
||||
Phase: resources.PhaseUploadConfigKubelet,
|
||||
},
|
||||
&resources.KubeadmPhase{
|
||||
Name: "bootstrap-token",
|
||||
Client: c,
|
||||
Log: log,
|
||||
Phase: resources.PhaseBootstrapToken,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getKubeadmAddonResources(c client.Client, log logr.Logger) []resources.Resource {
|
||||
func getKubeadmAddonResources(c client.Client) []resources.Resource {
|
||||
return []resources.Resource{
|
||||
&resources.KubeadmAddonResource{
|
||||
Name: "coredns",
|
||||
Client: c,
|
||||
Log: log,
|
||||
KubeadmAddon: resources.AddonCoreDNS,
|
||||
},
|
||||
&resources.KubeadmAddonResource{
|
||||
Name: "kubeproxy",
|
||||
Client: c,
|
||||
Log: log,
|
||||
KubeadmAddon: resources.AddonKubeProxy,
|
||||
},
|
||||
}
|
||||
@@ -249,44 +235,19 @@ func getKubeadmAddonResources(c client.Client, log logr.Logger) []resources.Reso
|
||||
|
||||
func getExternalKonnectivityResources(c client.Client) []resources.Resource {
|
||||
return []resources.Resource{
|
||||
&konnectivity.ServiceAccountResource{
|
||||
Client: c,
|
||||
Name: "konnectivity-sa",
|
||||
},
|
||||
&konnectivity.ClusterRoleBindingResource{
|
||||
Client: c,
|
||||
Name: "konnectivity-clusterrolebinding",
|
||||
},
|
||||
&konnectivity.KubernetesDeploymentResource{
|
||||
Client: c,
|
||||
Name: "konnectivity-deployment",
|
||||
},
|
||||
&konnectivity.ServiceResource{
|
||||
Client: c,
|
||||
Name: "konnectivity-service",
|
||||
},
|
||||
&konnectivity.Agent{
|
||||
Client: c,
|
||||
Name: "konnectivity-agent",
|
||||
},
|
||||
&konnectivity.ServiceAccountResource{Client: c},
|
||||
&konnectivity.ClusterRoleBindingResource{Client: c},
|
||||
&konnectivity.KubernetesDeploymentResource{Client: c},
|
||||
&konnectivity.ServiceResource{Client: c},
|
||||
&konnectivity.Agent{Client: c},
|
||||
}
|
||||
}
|
||||
|
||||
func getInternalKonnectivityResources(c client.Client, log logr.Logger) []resources.Resource {
|
||||
func getInternalKonnectivityResources(c client.Client) []resources.Resource {
|
||||
return []resources.Resource{
|
||||
&konnectivity.EgressSelectorConfigurationResource{
|
||||
Client: c,
|
||||
Name: "konnectivity-egress-selector-configuration",
|
||||
},
|
||||
&konnectivity.CertificateResource{
|
||||
Client: c,
|
||||
Log: log,
|
||||
Name: "konnectivity-certificate",
|
||||
},
|
||||
&konnectivity.KubeconfigResource{
|
||||
Client: c,
|
||||
Name: "konnectivity-kubeconfig",
|
||||
},
|
||||
&konnectivity.EgressSelectorConfigurationResource{Client: c},
|
||||
&konnectivity.CertificateResource{Client: c},
|
||||
&konnectivity.KubeconfigResource{Client: c},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
finalizer = "finalizer.kamaji.clastix.io"
|
||||
tenantControlPlaneFinalizer = "finalizer.kamaji.clastix.io"
|
||||
)
|
||||
|
||||
// TenantControlPlaneReconciler reconciles a TenantControlPlane object.
|
||||
@@ -42,9 +42,9 @@ type TenantControlPlaneReconciler struct {
|
||||
|
||||
// TenantControlPlaneReconcilerConfig gives the necessary configuration for TenantControlPlaneReconciler.
|
||||
type TenantControlPlaneReconcilerConfig struct {
|
||||
DataStoreName string
|
||||
KineContainerImage string
|
||||
TmpBaseDirectory string
|
||||
DefaultDataStoreName string
|
||||
KineContainerImage string
|
||||
TmpBaseDirectory string
|
||||
}
|
||||
|
||||
//+kubebuilder:rbac:groups=kamaji.clastix.io,resources=tenantcontrolplanes,verbs=get;list;watch;create;update;patch;delete
|
||||
@@ -62,6 +62,8 @@ func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.R
|
||||
tenantControlPlane := &kamajiv1alpha1.TenantControlPlane{}
|
||||
isTenantControlPlane, err := r.getTenantControlPlane(ctx, req.NamespacedName, tenantControlPlane)
|
||||
if err != nil {
|
||||
log.Error(err, "cannot retrieve the required instance")
|
||||
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
if !isTenantControlPlane {
|
||||
@@ -69,19 +71,23 @@ func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.R
|
||||
}
|
||||
|
||||
markedToBeDeleted := tenantControlPlane.GetDeletionTimestamp() != nil
|
||||
hasFinalizer := hasFinalizer(*tenantControlPlane)
|
||||
hasFinalizer := controllerutil.ContainsFinalizer(tenantControlPlane, tenantControlPlaneFinalizer)
|
||||
|
||||
if markedToBeDeleted && !hasFinalizer {
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
// Retrieving the DataStore to use for the current reconciliation
|
||||
ds, err := r.dataStore(ctx, tenantControlPlane)
|
||||
if err != nil {
|
||||
log.Error(err, "cannot retrieve the DataStore for the given instance")
|
||||
|
||||
ds := kamajiv1alpha1.DataStore{}
|
||||
if err = r.Client.Get(ctx, k8stypes.NamespacedName{Name: r.Config.DataStoreName}, &ds); err != nil {
|
||||
return ctrl.Result{}, errors.Wrap(err, "cannot retrieve kamajiv1alpha.DataStore object")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
dsConnection, err := r.getStorageConnection(ctx, ds)
|
||||
dsConnection, err := r.getStorageConnection(ctx, *ds)
|
||||
if err != nil {
|
||||
log.Error(err, "cannot generate the DataStore connection for the given instance")
|
||||
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
defer dsConnection.Close()
|
||||
@@ -100,6 +106,8 @@ func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.R
|
||||
|
||||
for _, resource := range registeredDeletableResources {
|
||||
if err = resources.HandleDeletion(ctx, resource, tenantControlPlane); err != nil {
|
||||
log.Error(err, "resource deletion failed", "resource", resource.GetName())
|
||||
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
@@ -108,6 +116,8 @@ func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.R
|
||||
log.Info("removing finalizer")
|
||||
|
||||
if err = r.RemoveFinalizer(ctx, tenantControlPlane); err != nil {
|
||||
log.Error(err, "cannot remove the finalizer for the given resource")
|
||||
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
@@ -126,7 +136,7 @@ func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.R
|
||||
log: log,
|
||||
tcpReconcilerConfig: r.Config,
|
||||
tenantControlPlane: *tenantControlPlane,
|
||||
DataStore: ds,
|
||||
DataStore: *ds,
|
||||
Connection: dsConnection,
|
||||
}
|
||||
registeredResources := GetResources(groupResourceBuilderConfiguration)
|
||||
@@ -140,6 +150,8 @@ func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.R
|
||||
return ctrl.Result{Requeue: true}, nil
|
||||
}
|
||||
|
||||
log.Error(err, "handling of resource failed", "resource", resource.GetName())
|
||||
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
@@ -148,6 +160,8 @@ func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.R
|
||||
}
|
||||
|
||||
if err := r.updateStatus(ctx, req.NamespacedName, resource); err != nil {
|
||||
log.Error(err, "update of the resource failed", "resource", resource.GetName())
|
||||
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
@@ -215,24 +229,34 @@ func (r *TenantControlPlaneReconciler) updateStatus(ctx context.Context, namespa
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasFinalizer(tenantControlPlane kamajiv1alpha1.TenantControlPlane) bool {
|
||||
for _, f := range tenantControlPlane.GetFinalizers() {
|
||||
if f == finalizer {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *TenantControlPlaneReconciler) AddFinalizer(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
controllerutil.AddFinalizer(tenantControlPlane, finalizer)
|
||||
controllerutil.AddFinalizer(tenantControlPlane, tenantControlPlaneFinalizer)
|
||||
|
||||
return r.Update(ctx, tenantControlPlane)
|
||||
}
|
||||
|
||||
func (r *TenantControlPlaneReconciler) RemoveFinalizer(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
controllerutil.RemoveFinalizer(tenantControlPlane, finalizer)
|
||||
controllerutil.RemoveFinalizer(tenantControlPlane, tenantControlPlaneFinalizer)
|
||||
|
||||
return r.Update(ctx, tenantControlPlane)
|
||||
}
|
||||
|
||||
// dataStore retrieves the override DataStore for the given Tenant Control Plane if specified,
|
||||
// otherwise fallback to the default one specified in the Kamaji setup.
|
||||
func (r *TenantControlPlaneReconciler) dataStore(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (*kamajiv1alpha1.DataStore, error) {
|
||||
dataStoreName := tenantControlPlane.Spec.DataStore
|
||||
if len(dataStoreName) == 0 {
|
||||
dataStoreName = r.Config.DefaultDataStoreName
|
||||
}
|
||||
|
||||
if statusDataStore := tenantControlPlane.Status.Storage.DataStoreName; len(statusDataStore) > 0 && dataStoreName != statusDataStore {
|
||||
return nil, fmt.Errorf("migration from a DataStore (current: %s) to another one (desired: %s) is not yet supported", statusDataStore, dataStoreName)
|
||||
}
|
||||
|
||||
ds := &kamajiv1alpha1.DataStore{}
|
||||
if err := r.Client.Get(ctx, k8stypes.NamespacedName{Name: dataStoreName}, ds); err != nil {
|
||||
return nil, errors.Wrap(err, "cannot retrieve *kamajiv1alpha.DataStore object")
|
||||
}
|
||||
|
||||
return ds, nil
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
include etcd/Makefile
|
||||
|
||||
deploy_path := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
|
||||
|
||||
.DEFAULT_GOAL := kamaji
|
||||
|
||||
.PHONY: etcd-cluster
|
||||
reqs: etcd-cluster
|
||||
|
||||
.PHONY: kamaji
|
||||
kamaji: reqs
|
||||
@kubectl apply -f $(deploy_path)/../../config/install.yaml
|
||||
|
||||
.PHONY: destroy
|
||||
destroy: etcd-certificates/cleanup
|
||||
@kubectl delete -f $(deploy_path)/../../config/install.yaml
|
||||
@@ -1,26 +0,0 @@
|
||||
# Deploy Kamaji
|
||||
|
||||
## Quickstart with KinD
|
||||
|
||||
```sh
|
||||
make -C kind
|
||||
```
|
||||
|
||||
## Multi-tenant etcd cluster
|
||||
|
||||
> This assumes you already have a running Kubernetes cluster and kubeconfig.
|
||||
|
||||
```sh
|
||||
make -C etcd
|
||||
```
|
||||
|
||||
## Multi-tenant cluster using Kine
|
||||
|
||||
`kine` is an `etcd` shim that allows using different datastore.
|
||||
|
||||
Kamaji actually support the following backends:
|
||||
|
||||
- [MySQL](kine/mysql/README.md)
|
||||
- [PostgreSQL](kine/postgresql/README.md)
|
||||
|
||||
> This assumes you already have a running Kubernetes cluster and kubeconfig.
|
||||
@@ -1,680 +0,0 @@
|
||||
---
|
||||
# Source: calico/templates/calico-config.yaml
|
||||
# This ConfigMap is used to configure a self-hosted Calico installation.
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: calico-config
|
||||
namespace: kube-system
|
||||
data:
|
||||
# Typha is disabled.
|
||||
typha_service_name: "none"
|
||||
# Configure the backend to use.
|
||||
calico_backend: "vxlan"
|
||||
|
||||
# Configure the MTU to use for workload interfaces and tunnels.
|
||||
# By default, MTU is auto-detected, and explicitly setting this field should not be required.
|
||||
# You can override auto-detection by providing a non-zero value.
|
||||
veth_mtu: "0"
|
||||
|
||||
# The CNI network configuration to install on each node. The special
|
||||
# values in this config will be automatically populated.
|
||||
cni_network_config: |-
|
||||
{
|
||||
"name": "k8s-pod-network",
|
||||
"cniVersion": "0.3.1",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "calico",
|
||||
"log_level": "info",
|
||||
"log_file_path": "/var/log/calico/cni/cni.log",
|
||||
"datastore_type": "kubernetes",
|
||||
"nodename": "__KUBERNETES_NODE_NAME__",
|
||||
"mtu": __CNI_MTU__,
|
||||
"ipam": {
|
||||
"type": "calico-ipam"
|
||||
},
|
||||
"policy": {
|
||||
"type": "k8s"
|
||||
},
|
||||
"kubernetes": {
|
||||
"kubeconfig": "__KUBECONFIG_FILEPATH__"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "portmap",
|
||||
"snat": true,
|
||||
"capabilities": {"portMappings": true}
|
||||
},
|
||||
{
|
||||
"type": "bandwidth",
|
||||
"capabilities": {"bandwidth": true}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: calico-node
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods
|
||||
- nodes
|
||||
- namespaces
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- discovery.k8s.io
|
||||
resources:
|
||||
- endpointslices
|
||||
verbs:
|
||||
- watch
|
||||
- list
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- endpoints
|
||||
- services
|
||||
verbs:
|
||||
- watch
|
||||
- list
|
||||
- get
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- nodes/status
|
||||
verbs:
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- networkpolicies
|
||||
verbs:
|
||||
- watch
|
||||
- list
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods
|
||||
- namespaces
|
||||
- serviceaccounts
|
||||
verbs:
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods/status
|
||||
verbs:
|
||||
- patch
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- globalfelixconfigs
|
||||
- felixconfigurations
|
||||
- bgppeers
|
||||
- globalbgpconfigs
|
||||
- bgpconfigurations
|
||||
- ippools
|
||||
- ipamblocks
|
||||
- globalnetworkpolicies
|
||||
- globalnetworksets
|
||||
- networkpolicies
|
||||
- networksets
|
||||
- clusterinformations
|
||||
- hostendpoints
|
||||
- blockaffinities
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- ippools
|
||||
- felixconfigurations
|
||||
- clusterinformations
|
||||
verbs:
|
||||
- create
|
||||
- update
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- nodes
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- bgpconfigurations
|
||||
- bgppeers
|
||||
verbs:
|
||||
- create
|
||||
- update
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- blockaffinities
|
||||
- ipamblocks
|
||||
- ipamhandles
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- create
|
||||
- update
|
||||
- delete
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- ipamconfigs
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- blockaffinities
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- daemonsets
|
||||
verbs:
|
||||
- get
|
||||
|
||||
---
|
||||
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: calico-kube-controllers
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- nodes
|
||||
verbs:
|
||||
- watch
|
||||
- list
|
||||
- get
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- ippools
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- blockaffinities
|
||||
- ipamblocks
|
||||
- ipamhandles
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- create
|
||||
- update
|
||||
- delete
|
||||
- watch
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- hostendpoints
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- create
|
||||
- update
|
||||
- delete
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- clusterinformations
|
||||
verbs:
|
||||
- get
|
||||
- create
|
||||
- update
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- kubecontrollersconfigurations
|
||||
verbs:
|
||||
- get
|
||||
- create
|
||||
- update
|
||||
- watch
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: calico-node
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: calico-node
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: calico-node
|
||||
namespace: kube-system
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: calico-kube-controllers
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: calico-kube-controllers
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: calico-kube-controllers
|
||||
namespace: kube-system
|
||||
---
|
||||
# Source: calico/templates/calico-node.yaml
|
||||
# This manifest installs the calico-node container, as well
|
||||
# as the CNI plugins and network config on
|
||||
# each master and worker node in a Kubernetes cluster.
|
||||
kind: DaemonSet
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: calico-node
|
||||
namespace: kube-system
|
||||
labels:
|
||||
k8s-app: calico-node
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
k8s-app: calico-node
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxUnavailable: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: calico-node
|
||||
spec:
|
||||
nodeSelector:
|
||||
kubernetes.io/os: linux
|
||||
hostNetwork: true
|
||||
tolerations:
|
||||
# Make sure calico-node gets scheduled on all nodes.
|
||||
- effect: NoSchedule
|
||||
operator: Exists
|
||||
# Mark the pod as a critical add-on for rescheduling.
|
||||
- key: CriticalAddonsOnly
|
||||
operator: Exists
|
||||
- effect: NoExecute
|
||||
operator: Exists
|
||||
serviceAccountName: calico-node
|
||||
# Minimize downtime during a rolling upgrade or deletion; tell Kubernetes to do a "force
|
||||
# deletion": https://kubernetes.io/docs/concepts/workloads/pods/pod/#termination-of-pods.
|
||||
terminationGracePeriodSeconds: 0
|
||||
priorityClassName: system-node-critical
|
||||
initContainers:
|
||||
# This container performs upgrade from host-local IPAM to calico-ipam.
|
||||
# It can be deleted if this is a fresh installation, or if you have already
|
||||
# upgraded to use calico-ipam.
|
||||
- name: upgrade-ipam
|
||||
image: docker.io/calico/cni:v3.20.0
|
||||
command: ["/opt/cni/bin/calico-ipam", "-upgrade"]
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
# Allow KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT to be overridden for eBPF mode.
|
||||
name: kubernetes-services-endpoint
|
||||
optional: true
|
||||
env:
|
||||
- name: KUBERNETES_NODE_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: spec.nodeName
|
||||
- name: CALICO_NETWORKING_BACKEND
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: calico-config
|
||||
key: calico_backend
|
||||
volumeMounts:
|
||||
- mountPath: /var/lib/cni/networks
|
||||
name: host-local-net-dir
|
||||
- mountPath: /host/opt/cni/bin
|
||||
name: cni-bin-dir
|
||||
securityContext:
|
||||
privileged: true
|
||||
# This container installs the CNI binaries
|
||||
# and CNI network config file on each node.
|
||||
- name: install-cni
|
||||
image: docker.io/calico/cni:v3.20.0
|
||||
command: ["/opt/cni/bin/install"]
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
# Allow KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT to be overridden for eBPF mode.
|
||||
name: kubernetes-services-endpoint
|
||||
optional: true
|
||||
env:
|
||||
# Name of the CNI config file to create.
|
||||
- name: CNI_CONF_NAME
|
||||
value: "10-calico.conflist"
|
||||
# The CNI network config to install on each node.
|
||||
- name: CNI_NETWORK_CONFIG
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: calico-config
|
||||
key: cni_network_config
|
||||
# Set the hostname based on the k8s node name.
|
||||
- name: KUBERNETES_NODE_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: spec.nodeName
|
||||
# CNI MTU Config variable
|
||||
- name: CNI_MTU
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: calico-config
|
||||
key: veth_mtu
|
||||
# Prevents the container from sleeping forever.
|
||||
- name: SLEEP
|
||||
value: "false"
|
||||
volumeMounts:
|
||||
- mountPath: /host/opt/cni/bin
|
||||
name: cni-bin-dir
|
||||
- mountPath: /host/etc/cni/net.d
|
||||
name: cni-net-dir
|
||||
securityContext:
|
||||
privileged: true
|
||||
# Adds a Flex Volume Driver that creates a per-pod Unix Domain Socket to allow Dikastes
|
||||
# to communicate with Felix over the Policy Sync API.
|
||||
- name: flexvol-driver
|
||||
image: docker.io/calico/pod2daemon-flexvol:v3.20.0
|
||||
volumeMounts:
|
||||
- name: flexvol-driver-host
|
||||
mountPath: /host/driver
|
||||
securityContext:
|
||||
privileged: true
|
||||
containers:
|
||||
# Runs calico-node container on each Kubernetes node. This
|
||||
# container programs network policy and routes on each
|
||||
# host.
|
||||
- name: calico-node
|
||||
image: docker.io/calico/node:v3.20.0
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
# Allow KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT to be overridden for eBPF mode.
|
||||
name: kubernetes-services-endpoint
|
||||
optional: true
|
||||
env:
|
||||
# Use Kubernetes API as the backing datastore.
|
||||
- name: DATASTORE_TYPE
|
||||
value: "kubernetes"
|
||||
# Wait for the datastore.
|
||||
- name: WAIT_FOR_DATASTORE
|
||||
value: "true"
|
||||
# Set based on the k8s node name.
|
||||
- name: NODENAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: spec.nodeName
|
||||
# Choose the backend to use.
|
||||
- name: CALICO_NETWORKING_BACKEND
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: calico-config
|
||||
key: calico_backend
|
||||
# Cluster type to identify the deployment type
|
||||
- name: CLUSTER_TYPE
|
||||
value: "k8s"
|
||||
# Auto-detect the BGP IP address.
|
||||
- name: IP
|
||||
value: "autodetect"
|
||||
# Enable IPIP
|
||||
- name: CALICO_IPV4POOL_IPIP
|
||||
value: "Never"
|
||||
# Enable or Disable VXLAN on the default IP pool.
|
||||
- name: CALICO_IPV4POOL_VXLAN
|
||||
value: "Always"
|
||||
# Set MTU for tunnel device used if ipip is enabled
|
||||
- name: FELIX_IPINIPMTU
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: calico-config
|
||||
key: veth_mtu
|
||||
# Set MTU for the VXLAN tunnel device.
|
||||
- name: FELIX_VXLANMTU
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: calico-config
|
||||
key: veth_mtu
|
||||
# Set MTU for the Wireguard tunnel device.
|
||||
- name: FELIX_WIREGUARDMTU
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: calico-config
|
||||
key: veth_mtu
|
||||
# The default IPv4 pool to create on startup if none exists. Pod IPs will be
|
||||
# chosen from this range. Changing this value after installation will have
|
||||
# no effect. This should fall within `--cluster-cidr`.
|
||||
- name: CALICO_IPV4POOL_CIDR
|
||||
value: "10.36.0.0/16"
|
||||
# Disable file logging so `kubectl logs` works.
|
||||
- name: CALICO_DISABLE_FILE_LOGGING
|
||||
value: "true"
|
||||
# Set Felix endpoint to host default action to ACCEPT.
|
||||
- name: FELIX_DEFAULTENDPOINTTOHOSTACTION
|
||||
value: "ACCEPT"
|
||||
# Disable IPv6 on Kubernetes.
|
||||
- name: FELIX_IPV6SUPPORT
|
||||
value: "false"
|
||||
- name: FELIX_HEALTHENABLED
|
||||
value: "true"
|
||||
securityContext:
|
||||
privileged: true
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /bin/calico-node
|
||||
- -felix-live
|
||||
#- -bird-live
|
||||
periodSeconds: 10
|
||||
initialDelaySeconds: 10
|
||||
failureThreshold: 6
|
||||
timeoutSeconds: 10
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /bin/calico-node
|
||||
- -felix-ready
|
||||
#- -bird-ready
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 10
|
||||
volumeMounts:
|
||||
# For maintaining CNI plugin API credentials.
|
||||
- mountPath: /host/etc/cni/net.d
|
||||
name: cni-net-dir
|
||||
readOnly: false
|
||||
- mountPath: /lib/modules
|
||||
name: lib-modules
|
||||
readOnly: true
|
||||
- mountPath: /run/xtables.lock
|
||||
name: xtables-lock
|
||||
readOnly: false
|
||||
- mountPath: /var/run/calico
|
||||
name: var-run-calico
|
||||
readOnly: false
|
||||
- mountPath: /var/lib/calico
|
||||
name: var-lib-calico
|
||||
readOnly: false
|
||||
- name: policysync
|
||||
mountPath: /var/run/nodeagent
|
||||
# For eBPF mode, we need to be able to mount the BPF filesystem at /sys/fs/bpf so we mount in the
|
||||
# parent directory.
|
||||
- name: sysfs
|
||||
mountPath: /sys/fs/
|
||||
# Bidirectional means that, if we mount the BPF filesystem at /sys/fs/bpf it will propagate to the host.
|
||||
# If the host is known to mount that filesystem already then Bidirectional can be omitted.
|
||||
mountPropagation: Bidirectional
|
||||
- name: cni-log-dir
|
||||
mountPath: /var/log/calico/cni
|
||||
readOnly: true
|
||||
volumes:
|
||||
# Used by calico-node.
|
||||
- name: lib-modules
|
||||
hostPath:
|
||||
path: /lib/modules
|
||||
- name: var-run-calico
|
||||
hostPath:
|
||||
path: /var/run/calico
|
||||
- name: var-lib-calico
|
||||
hostPath:
|
||||
path: /var/lib/calico
|
||||
- name: xtables-lock
|
||||
hostPath:
|
||||
path: /run/xtables.lock
|
||||
type: FileOrCreate
|
||||
- name: sysfs
|
||||
hostPath:
|
||||
path: /sys/fs/
|
||||
type: DirectoryOrCreate
|
||||
# Used to install CNI.
|
||||
- name: cni-bin-dir
|
||||
hostPath:
|
||||
path: /opt/cni/bin
|
||||
- name: cni-net-dir
|
||||
hostPath:
|
||||
path: /etc/cni/net.d
|
||||
# Used to access CNI logs.
|
||||
- name: cni-log-dir
|
||||
hostPath:
|
||||
path: /var/log/calico/cni
|
||||
# Mount in the directory for host-local IPAM allocations. This is
|
||||
# used when upgrading from host-local to calico-ipam, and can be removed
|
||||
# if not using the upgrade-ipam init container.
|
||||
- name: host-local-net-dir
|
||||
hostPath:
|
||||
path: /var/lib/cni/networks
|
||||
# Used to create per-pod Unix Domain Sockets
|
||||
- name: policysync
|
||||
hostPath:
|
||||
type: DirectoryOrCreate
|
||||
path: /var/run/nodeagent
|
||||
# Used to install Flex Volume Driver
|
||||
- name: flexvol-driver-host
|
||||
hostPath:
|
||||
type: DirectoryOrCreate
|
||||
path: /usr/libexec/kubernetes/kubelet-plugins/volume/exec/nodeagent~uds
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: calico-node
|
||||
namespace: kube-system
|
||||
|
||||
---
|
||||
# Source: calico/templates/calico-kube-controllers.yaml
|
||||
# See https://github.com/projectcalico/kube-controllers
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: calico-kube-controllers
|
||||
namespace: kube-system
|
||||
labels:
|
||||
k8s-app: calico-kube-controllers
|
||||
spec:
|
||||
# The controllers can only have a single active instance.
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
k8s-app: calico-kube-controllers
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
name: calico-kube-controllers
|
||||
namespace: kube-system
|
||||
labels:
|
||||
k8s-app: calico-kube-controllers
|
||||
spec:
|
||||
tolerations:
|
||||
# Mark the pod as a critical add-on for rescheduling.
|
||||
- key: CriticalAddonsOnly
|
||||
operator: Exists
|
||||
- key: node-role.kubernetes.io/master
|
||||
effect: NoSchedule
|
||||
serviceAccountName: calico-kube-controllers
|
||||
priorityClassName: system-cluster-critical
|
||||
containers:
|
||||
- name: calico-kube-controllers
|
||||
image: docker.io/calico/kube-controllers:v3.20.0
|
||||
resouces:
|
||||
env:
|
||||
# Choose which controllers to run.
|
||||
- name: ENABLED_CONTROLLERS
|
||||
value: node
|
||||
- name: DATASTORE_TYPE
|
||||
value: kubernetes
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /usr/bin/check-status
|
||||
- -l
|
||||
periodSeconds: 10
|
||||
initialDelaySeconds: 10
|
||||
failureThreshold: 6
|
||||
timeoutSeconds: 10
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /usr/bin/check-status
|
||||
- -r
|
||||
periodSeconds: 10
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: calico-kube-controllers
|
||||
namespace: kube-system
|
||||
|
||||
---
|
||||
|
||||
# This manifest creates a Pod Disruption Budget for Controller to allow K8s Cluster Autoscaler to evict
|
||||
|
||||
apiVersion: policy/v1beta1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: calico-kube-controllers
|
||||
namespace: kube-system
|
||||
labels:
|
||||
k8s-app: calico-kube-controllers
|
||||
spec:
|
||||
maxUnavailable: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
k8s-app: calico-kube-controllers
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,687 +0,0 @@
|
||||
---
|
||||
# Source: calico/templates/calico-config.yaml
|
||||
# This ConfigMap is used to configure a self-hosted Calico installation.
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: calico-config
|
||||
namespace: kube-system
|
||||
data:
|
||||
# Typha is disabled.
|
||||
typha_service_name: "none"
|
||||
# Configure the backend to use.
|
||||
calico_backend: "bird"
|
||||
|
||||
# Configure the MTU to use for workload interfaces and tunnels.
|
||||
# By default, MTU is auto-detected, and explicitly setting this field should not be required.
|
||||
# You can override auto-detection by providing a non-zero value.
|
||||
veth_mtu: "0"
|
||||
|
||||
# The CNI network configuration to install on each node. The special
|
||||
# values in this config will be automatically populated.
|
||||
cni_network_config: |-
|
||||
{
|
||||
"name": "k8s-pod-network",
|
||||
"cniVersion": "0.3.1",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "calico",
|
||||
"log_level": "info",
|
||||
"log_file_path": "/var/log/calico/cni/cni.log",
|
||||
"datastore_type": "kubernetes",
|
||||
"nodename": "__KUBERNETES_NODE_NAME__",
|
||||
"mtu": __CNI_MTU__,
|
||||
"ipam": {
|
||||
"type": "calico-ipam"
|
||||
},
|
||||
"policy": {
|
||||
"type": "k8s"
|
||||
},
|
||||
"kubernetes": {
|
||||
"kubeconfig": "__KUBECONFIG_FILEPATH__"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "portmap",
|
||||
"snat": true,
|
||||
"capabilities": {"portMappings": true}
|
||||
},
|
||||
{
|
||||
"type": "bandwidth",
|
||||
"capabilities": {"bandwidth": true}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: calico-node
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods
|
||||
- nodes
|
||||
- namespaces
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- discovery.k8s.io
|
||||
resources:
|
||||
- endpointslices
|
||||
verbs:
|
||||
- watch
|
||||
- list
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- endpoints
|
||||
- services
|
||||
verbs:
|
||||
- watch
|
||||
- list
|
||||
- get
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- nodes/status
|
||||
verbs:
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- networkpolicies
|
||||
verbs:
|
||||
- watch
|
||||
- list
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods
|
||||
- namespaces
|
||||
- serviceaccounts
|
||||
verbs:
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods/status
|
||||
verbs:
|
||||
- patch
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- globalfelixconfigs
|
||||
- felixconfigurations
|
||||
- bgppeers
|
||||
- globalbgpconfigs
|
||||
- bgpconfigurations
|
||||
- ippools
|
||||
- ipamblocks
|
||||
- globalnetworkpolicies
|
||||
- globalnetworksets
|
||||
- networkpolicies
|
||||
- networksets
|
||||
- clusterinformations
|
||||
- hostendpoints
|
||||
- blockaffinities
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- ippools
|
||||
- felixconfigurations
|
||||
- clusterinformations
|
||||
verbs:
|
||||
- create
|
||||
- update
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- nodes
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- bgpconfigurations
|
||||
- bgppeers
|
||||
verbs:
|
||||
- create
|
||||
- update
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- blockaffinities
|
||||
- ipamblocks
|
||||
- ipamhandles
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- create
|
||||
- update
|
||||
- delete
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- ipamconfigs
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- blockaffinities
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- daemonsets
|
||||
verbs:
|
||||
- get
|
||||
|
||||
---
|
||||
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: calico-kube-controllers
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- nodes
|
||||
verbs:
|
||||
- watch
|
||||
- list
|
||||
- get
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- ippools
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- blockaffinities
|
||||
- ipamblocks
|
||||
- ipamhandles
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- create
|
||||
- update
|
||||
- delete
|
||||
- watch
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- hostendpoints
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- create
|
||||
- update
|
||||
- delete
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- clusterinformations
|
||||
verbs:
|
||||
- get
|
||||
- create
|
||||
- update
|
||||
- apiGroups:
|
||||
- crd.projectcalico.org
|
||||
resources:
|
||||
- kubecontrollersconfigurations
|
||||
verbs:
|
||||
- get
|
||||
- create
|
||||
- update
|
||||
- watch
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: calico-node
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: calico-node
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: calico-node
|
||||
namespace: kube-system
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: calico-kube-controllers
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: calico-kube-controllers
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: calico-kube-controllers
|
||||
namespace: kube-system
|
||||
---
|
||||
# Source: calico/templates/calico-node.yaml
|
||||
# This manifest installs the calico-node container, as well
|
||||
# as the CNI plugins and network config on
|
||||
# each master and worker node in a Kubernetes cluster.
|
||||
kind: DaemonSet
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: calico-node
|
||||
namespace: kube-system
|
||||
labels:
|
||||
k8s-app: calico-node
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
k8s-app: calico-node
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxUnavailable: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: calico-node
|
||||
spec:
|
||||
nodeSelector:
|
||||
kubernetes.io/os: linux
|
||||
hostNetwork: true
|
||||
tolerations:
|
||||
# Make sure calico-node gets scheduled on all nodes.
|
||||
- effect: NoSchedule
|
||||
operator: Exists
|
||||
# Mark the pod as a critical add-on for rescheduling.
|
||||
- key: CriticalAddonsOnly
|
||||
operator: Exists
|
||||
- effect: NoExecute
|
||||
operator: Exists
|
||||
serviceAccountName: calico-node
|
||||
# Minimize downtime during a rolling upgrade or deletion; tell Kubernetes to do a "force
|
||||
# deletion": https://kubernetes.io/docs/concepts/workloads/pods/pod/#termination-of-pods.
|
||||
terminationGracePeriodSeconds: 0
|
||||
priorityClassName: system-node-critical
|
||||
initContainers:
|
||||
# This container performs upgrade from host-local IPAM to calico-ipam.
|
||||
# It can be deleted if this is a fresh installation, or if you have already
|
||||
# upgraded to use calico-ipam.
|
||||
- name: upgrade-ipam
|
||||
image: docker.io/calico/cni:v3.20.0
|
||||
command: ["/opt/cni/bin/calico-ipam", "-upgrade"]
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
# Allow KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT to be overridden for eBPF mode.
|
||||
name: kubernetes-services-endpoint
|
||||
optional: true
|
||||
env:
|
||||
- name: KUBERNETES_NODE_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: spec.nodeName
|
||||
- name: CALICO_NETWORKING_BACKEND
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: calico-config
|
||||
key: calico_backend
|
||||
volumeMounts:
|
||||
- mountPath: /var/lib/cni/networks
|
||||
name: host-local-net-dir
|
||||
- mountPath: /host/opt/cni/bin
|
||||
name: cni-bin-dir
|
||||
securityContext:
|
||||
privileged: true
|
||||
# This container installs the CNI binaries
|
||||
# and CNI network config file on each node.
|
||||
- name: install-cni
|
||||
image: docker.io/calico/cni:v3.20.0
|
||||
command: ["/opt/cni/bin/install"]
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
# Allow KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT to be overridden for eBPF mode.
|
||||
name: kubernetes-services-endpoint
|
||||
optional: true
|
||||
env:
|
||||
# Name of the CNI config file to create.
|
||||
- name: CNI_CONF_NAME
|
||||
value: "10-calico.conflist"
|
||||
# The CNI network config to install on each node.
|
||||
- name: CNI_NETWORK_CONFIG
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: calico-config
|
||||
key: cni_network_config
|
||||
# Set the hostname based on the k8s node name.
|
||||
- name: KUBERNETES_NODE_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: spec.nodeName
|
||||
# CNI MTU Config variable
|
||||
- name: CNI_MTU
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: calico-config
|
||||
key: veth_mtu
|
||||
# Prevents the container from sleeping forever.
|
||||
- name: SLEEP
|
||||
value: "false"
|
||||
volumeMounts:
|
||||
- mountPath: /host/opt/cni/bin
|
||||
name: cni-bin-dir
|
||||
- mountPath: /host/etc/cni/net.d
|
||||
name: cni-net-dir
|
||||
securityContext:
|
||||
privileged: true
|
||||
# Adds a Flex Volume Driver that creates a per-pod Unix Domain Socket to allow Dikastes
|
||||
# to communicate with Felix over the Policy Sync API.
|
||||
- name: flexvol-driver
|
||||
image: docker.io/calico/pod2daemon-flexvol:v3.20.0
|
||||
volumeMounts:
|
||||
- name: flexvol-driver-host
|
||||
mountPath: /host/driver
|
||||
securityContext:
|
||||
privileged: true
|
||||
containers:
|
||||
# Runs calico-node container on each Kubernetes node. This
|
||||
# container programs network policy and routes on each
|
||||
# host.
|
||||
- name: calico-node
|
||||
image: docker.io/calico/node:v3.20.0
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
# Allow KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT to be overridden for eBPF mode.
|
||||
name: kubernetes-services-endpoint
|
||||
optional: true
|
||||
env:
|
||||
# Use Kubernetes API as the backing datastore.
|
||||
- name: DATASTORE_TYPE
|
||||
value: "kubernetes"
|
||||
# Wait for the datastore.
|
||||
- name: WAIT_FOR_DATASTORE
|
||||
value: "true"
|
||||
# Set based on the k8s node name.
|
||||
- name: NODENAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: spec.nodeName
|
||||
# Choose the backend to use.
|
||||
- name: CALICO_NETWORKING_BACKEND
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: calico-config
|
||||
key: calico_backend
|
||||
# Cluster type to identify the deployment type
|
||||
- name: CLUSTER_TYPE
|
||||
value: "k8s"
|
||||
# Auto-detect the BGP IP address.
|
||||
- name: IP
|
||||
value: "autodetect"
|
||||
- name: IP_AUTODETECTION_METHOD
|
||||
value: "can-reach=192.168.32.0"
|
||||
# Enable IPIP
|
||||
- name: CALICO_IPV4POOL_IPIP
|
||||
value: "Never"
|
||||
# Enable or Disable VXLAN on the default IP pool.
|
||||
- name: CALICO_IPV4POOL_VXLAN
|
||||
value: "Never"
|
||||
# Set MTU for tunnel device used if ipip is enabled
|
||||
- name: FELIX_IPINIPMTU
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: calico-config
|
||||
key: veth_mtu
|
||||
# Set MTU for the VXLAN tunnel device.
|
||||
- name: FELIX_VXLANMTU
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: calico-config
|
||||
key: veth_mtu
|
||||
# Set MTU for the Wireguard tunnel device.
|
||||
- name: FELIX_WIREGUARDMTU
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: calico-config
|
||||
key: veth_mtu
|
||||
# The default IPv4 pool to create on startup if none exists. Pod IPs will be
|
||||
# chosen from this range. Changing this value after installation will have
|
||||
# no effect. This should fall within `--cluster-cidr`.
|
||||
# - name: CALICO_IPV4POOL_CIDR
|
||||
# value: "192.168.0.0/16"
|
||||
# Disable file logging so `kubectl logs` works.
|
||||
- name: CALICO_DISABLE_FILE_LOGGING
|
||||
value: "true"
|
||||
# Set Felix endpoint to host default action to ACCEPT.
|
||||
- name: FELIX_DEFAULTENDPOINTTOHOSTACTION
|
||||
value: "ACCEPT"
|
||||
# Disable IPv6 on Kubernetes.
|
||||
- name: FELIX_IPV6SUPPORT
|
||||
value: "false"
|
||||
- name: FELIX_HEALTHENABLED
|
||||
value: "true"
|
||||
securityContext:
|
||||
privileged: true
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /bin/calico-node
|
||||
- -felix-live
|
||||
- -bird-live
|
||||
periodSeconds: 10
|
||||
initialDelaySeconds: 10
|
||||
failureThreshold: 6
|
||||
timeoutSeconds: 10
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /bin/calico-node
|
||||
- -felix-ready
|
||||
- -bird-ready
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 10
|
||||
volumeMounts:
|
||||
# For maintaining CNI plugin API credentials.
|
||||
- mountPath: /host/etc/cni/net.d
|
||||
name: cni-net-dir
|
||||
readOnly: false
|
||||
- mountPath: /lib/modules
|
||||
name: lib-modules
|
||||
readOnly: true
|
||||
- mountPath: /run/xtables.lock
|
||||
name: xtables-lock
|
||||
readOnly: false
|
||||
- mountPath: /var/run/calico
|
||||
name: var-run-calico
|
||||
readOnly: false
|
||||
- mountPath: /var/lib/calico
|
||||
name: var-lib-calico
|
||||
readOnly: false
|
||||
- name: policysync
|
||||
mountPath: /var/run/nodeagent
|
||||
# For eBPF mode, we need to be able to mount the BPF filesystem at /sys/fs/bpf so we mount in the
|
||||
# parent directory.
|
||||
- name: sysfs
|
||||
mountPath: /sys/fs/
|
||||
# Bidirectional means that, if we mount the BPF filesystem at /sys/fs/bpf it will propagate to the host.
|
||||
# If the host is known to mount that filesystem already then Bidirectional can be omitted.
|
||||
mountPropagation: Bidirectional
|
||||
- name: cni-log-dir
|
||||
mountPath: /var/log/calico/cni
|
||||
readOnly: true
|
||||
volumes:
|
||||
# Used by calico-node.
|
||||
- name: lib-modules
|
||||
hostPath:
|
||||
path: /lib/modules
|
||||
- name: var-run-calico
|
||||
hostPath:
|
||||
path: /var/run/calico
|
||||
- name: var-lib-calico
|
||||
hostPath:
|
||||
path: /var/lib/calico
|
||||
- name: xtables-lock
|
||||
hostPath:
|
||||
path: /run/xtables.lock
|
||||
type: FileOrCreate
|
||||
- name: sysfs
|
||||
hostPath:
|
||||
path: /sys/fs/
|
||||
type: DirectoryOrCreate
|
||||
# Used to install CNI.
|
||||
- name: cni-bin-dir
|
||||
hostPath:
|
||||
path: /opt/cni/bin
|
||||
- name: cni-net-dir
|
||||
hostPath:
|
||||
path: /etc/cni/net.d
|
||||
# Used to access CNI logs.
|
||||
- name: cni-log-dir
|
||||
hostPath:
|
||||
path: /var/log/calico/cni
|
||||
# Mount in the directory for host-local IPAM allocations. This is
|
||||
# used when upgrading from host-local to calico-ipam, and can be removed
|
||||
# if not using the upgrade-ipam init container.
|
||||
- name: host-local-net-dir
|
||||
hostPath:
|
||||
path: /var/lib/cni/networks
|
||||
# Used to create per-pod Unix Domain Sockets
|
||||
- name: policysync
|
||||
hostPath:
|
||||
type: DirectoryOrCreate
|
||||
path: /var/run/nodeagent
|
||||
# Used to install Flex Volume Driver
|
||||
- name: flexvol-driver-host
|
||||
hostPath:
|
||||
type: DirectoryOrCreate
|
||||
path: /usr/libexec/kubernetes/kubelet-plugins/volume/exec/nodeagent~uds
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: calico-node
|
||||
namespace: kube-system
|
||||
|
||||
---
|
||||
# Source: calico/templates/calico-kube-controllers.yaml
|
||||
# See https://github.com/projectcalico/kube-controllers
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: calico-kube-controllers
|
||||
namespace: kube-system
|
||||
labels:
|
||||
k8s-app: calico-kube-controllers
|
||||
spec:
|
||||
# The controllers can only have a single active instance.
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
k8s-app: calico-kube-controllers
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
name: calico-kube-controllers
|
||||
namespace: kube-system
|
||||
labels:
|
||||
k8s-app: calico-kube-controllers
|
||||
spec:
|
||||
nodeSelector:
|
||||
kubernetes.io/os: linux
|
||||
tolerations:
|
||||
# Mark the pod as a critical add-on for rescheduling.
|
||||
- key: CriticalAddonsOnly
|
||||
operator: Exists
|
||||
- key: node-role.kubernetes.io/master
|
||||
effect: NoSchedule
|
||||
serviceAccountName: calico-kube-controllers
|
||||
priorityClassName: system-cluster-critical
|
||||
containers:
|
||||
- name: calico-kube-controllers
|
||||
image: docker.io/calico/kube-controllers:v3.20.0
|
||||
env:
|
||||
# Choose which controllers to run.
|
||||
- name: ENABLED_CONTROLLERS
|
||||
value: node
|
||||
- name: DATASTORE_TYPE
|
||||
value: kubernetes
|
||||
resources:
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /usr/bin/check-status
|
||||
- -l
|
||||
periodSeconds: 10
|
||||
initialDelaySeconds: 10
|
||||
failureThreshold: 6
|
||||
timeoutSeconds: 10
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /usr/bin/check-status
|
||||
- -r
|
||||
periodSeconds: 10
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: calico-kube-controllers
|
||||
namespace: kube-system
|
||||
|
||||
---
|
||||
|
||||
# This manifest creates a Pod Disruption Budget for Controller to allow K8s Cluster Autoscaler to evict
|
||||
|
||||
apiVersion: policy/v1beta1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: calico-kube-controllers
|
||||
namespace: kube-system
|
||||
labels:
|
||||
k8s-app: calico-kube-controllers
|
||||
spec:
|
||||
maxUnavailable: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
k8s-app: calico-kube-controllers
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -54,13 +54,6 @@ spec:
|
||||
app: etcd
|
||||
spec:
|
||||
serviceAccountName: etcd
|
||||
topologySpreadConstraints:
|
||||
- maxSkew: 1
|
||||
topologyKey: topology.kubernetes.io/zone
|
||||
whenUnsatisfiable: DoNotSchedule
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app: etcd
|
||||
volumes:
|
||||
- name: certs
|
||||
secret:
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
# azure parameters
|
||||
export KAMAJI_REGION=westeurope
|
||||
export KAMAJI_RG=Kamaji
|
||||
# https://docs.microsoft.com/en-us/azure/aks/faq#why-are-two-resource-groups-created-with-aks
|
||||
export KAMAJI_CLUSTER=kamaji
|
||||
export KAMAJI_NODE_RG=MC_${KAMAJI_RG}_${KAMAJI_CLUSTER}_${KAMAJI_REGION}
|
||||
export KAMAJI_CLUSTER=kamaji
|
||||
export KAMAJI_VNET_NAME=kamaji-net
|
||||
export KAMAJI_VNET_ADDRESS=10.224.0.0/12
|
||||
export KAMAJI_SUBNET_NAME=kamaji-subnet
|
||||
export KAMAJI_SUBNET_ADDRESS=10.224.0.0/16
|
||||
|
||||
# kamaji parameters
|
||||
export KAMAJI_NAMESPACE=kamaji-system
|
||||
|
||||
# tenant cluster parameters
|
||||
export TENANT_NAMESPACE=tenants
|
||||
export TENANT_NAMESPACE=default
|
||||
export TENANT_NAME=tenant-00
|
||||
export TENANT_DOMAIN=$KAMAJI_REGION.cloudapp.azure.com
|
||||
export TENANT_VERSION=v1.23.5
|
||||
export TENANT_VERSION=v1.25.0
|
||||
export TENANT_PORT=6443 # port used to expose the tenant api server
|
||||
export TENANT_PROXY_PORT=8132 # port used to expose the konnectivity server
|
||||
export TENANT_POD_CIDR=10.36.0.0/16
|
||||
@@ -21,10 +24,8 @@ export TENANT_DNS_SERVICE=10.96.0.10
|
||||
|
||||
export TENANT_VM_SIZE=Standard_D2ds_v4
|
||||
export TENANT_VM_IMAGE=UbuntuLTS
|
||||
export TENANT_RG=$TENANT_NAME
|
||||
export TENANT_NSG=$TENANT_NAME-nsg
|
||||
export TENANT_VNET_NAME=$TENANT_NAME
|
||||
export TENANT_VNET_ADDRESS=172.12.0.0/16
|
||||
export TENANT_SUBNET_NAME=$TENANT_NAME-subnet
|
||||
export TENANT_SUBNET_ADDRESS=172.12.10.0/24
|
||||
export TENANT_VMSS=$TENANT_NAME-vmss
|
||||
export TENANT_SUBNET_ADDRESS=10.225.0.0/16
|
||||
export TENANT_VMSS=$TENANT_NAME-vmss
|
||||
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
export KAMAJI_NAMESPACE=kamaji-system
|
||||
|
||||
# tenant cluster parameters
|
||||
export TENANT_NAMESPACE=tenants
|
||||
export TENANT_NAMESPACE=default
|
||||
export TENANT_NAME=tenant-00
|
||||
export TENANT_DOMAIN=clastix.labs
|
||||
export TENANT_VERSION=v1.23.5
|
||||
export TENANT_VERSION=v1.25.0
|
||||
export TENANT_PORT=6443 # port used to expose the tenant api server
|
||||
export TENANT_PROXY_PORT=8132 # port used to expose the konnectivity server
|
||||
export TENANT_POD_CIDR=10.36.0.0/16
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
# Kine integration
|
||||
|
||||
[kine](https://github.com/k3s-io/kine) is an `etcd` shim that allows to use a different datastore for your Kubernetes cluster.
|
||||
|
||||
Kamaji actually allows to run a shared datastore using different MySQL and PostgreSQL schemas per Tenant.
|
||||
This can help in overcoming the `etcd` limitation regarding scalability and cluster size, as well with HA and replication.
|
||||
|
||||
## Kamaji additional CLI flags
|
||||
|
||||
Kamaji read the data store configuration from a cluster-scoped resource named `DataStore`, containing all tha required details to secure a connection using a specific driver.
|
||||
|
||||
- [Example of a `etcd` DataStore](./../../config/samples/kamaji_v1alpha1_datastore_etcd.yaml)
|
||||
- [Example of a `MySQL` DataStore](./../../config/samples/kamaji_v1alpha1_datastore_mysql.yaml)
|
||||
- [Example of a `PostgreSQL` DataStore](./../../config/samples/kamaji_v1alpha1_datastore_postgresql.yaml)
|
||||
|
||||
Once the datastore is running, and the `DataStore` has been created with the required details, we need to provide information about it to Kamaji by using the following flag and pointing to the resource name:
|
||||
|
||||
```
|
||||
--datastore={.metadata.name}
|
||||
```
|
||||
|
||||
## Drivers
|
||||
|
||||
Further details on the setup for each driver are available here:
|
||||
- [MySQL/MariaDB](../deploy/kine/mysql/README.md)
|
||||
- [PostgreSQL](../deploy/kine/postgresql/README.md)
|
||||
@@ -15,6 +15,7 @@ mariadb-certificates:
|
||||
chmod 644 $(ROOT_DIR)/certs/*
|
||||
|
||||
mariadb-secret:
|
||||
@kubectl create namespace kamaji-system --dry-run=client -o yaml | kubectl apply -f -
|
||||
@kubectl -n kamaji-system create secret generic mysql-config \
|
||||
--from-file=$(ROOT_DIR)/certs/ca.crt --from-file=$(ROOT_DIR)/certs/ca.key \
|
||||
--from-file=$(ROOT_DIR)/certs/server.key --from-file=$(ROOT_DIR)/certs/server.crt \
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
# MySQL as Kubernetes Storage
|
||||
|
||||
Kamaji offers the possibility of having a different storage system than `ETCD` thanks to [kine](https://github.com/k3s-io/kine). One of the implementations is [MySQL](https://www.mysql.com/).
|
||||
|
||||
Kamaji project is developed using [kind](https://kind.sigs.k8s.io), therefore, MySQL (or [MariaDB](https://mariadb.org/) in this case) will be deployed into the local kubernetes cluster in order to be used as storage for the tenants.
|
||||
|
||||
There is a Makefile to help with the process:
|
||||
|
||||
# Setup
|
||||
|
||||
Setup of the MySQL/MariaDB backend can be easily issued with a single command.
|
||||
|
||||
```bash
|
||||
$ make mariadb
|
||||
```
|
||||
|
||||
This action will perform all the necessary stuffs to have MariaDB as Kubernetes storage backend using kine.
|
||||
|
||||
```shell
|
||||
rm -rf /home/prometherion/Documents/clastix/kamaji/deploy/mysql/certs && mkdir /home/prometherion/Documents/clastix/kamaji/deploy/mysql/certs
|
||||
cfssl gencert -initca /home/prometherion/Documents/clastix/kamaji/deploy/mysql/ca-csr.json | cfssljson -bare /home/prometherion/Documents/clastix/kamaji/deploy/mysql/certs/ca
|
||||
2022/08/18 23:52:56 [INFO] generating a new CA key and certificate from CSR
|
||||
2022/08/18 23:52:56 [INFO] generate received request
|
||||
2022/08/18 23:52:56 [INFO] received CSR
|
||||
2022/08/18 23:52:56 [INFO] generating key: rsa-2048
|
||||
2022/08/18 23:52:56 [INFO] encoded CSR
|
||||
2022/08/18 23:52:56 [INFO] signed certificate with serial number 310428005543054656774215122317606431230766314770
|
||||
cfssl gencert -ca=/home/prometherion/Documents/clastix/kamaji/deploy/mysql/certs/ca.crt -ca-key=/home/prometherion/Documents/clastix/kamaji/deploy/mysql/certs/ca.key \
|
||||
-config=/home/prometherion/Documents/clastix/kamaji/deploy/mysql/config.json -profile=server \
|
||||
/home/prometherion/Documents/clastix/kamaji/deploy/mysql/server-csr.json | cfssljson -bare /home/prometherion/Documents/clastix/kamaji/deploy/mysql/certs/server
|
||||
2022/08/18 23:52:56 [INFO] generate received request
|
||||
2022/08/18 23:52:56 [INFO] received CSR
|
||||
2022/08/18 23:52:56 [INFO] generating key: rsa-2048
|
||||
2022/08/18 23:52:56 [INFO] encoded CSR
|
||||
2022/08/18 23:52:56 [INFO] signed certificate with serial number 582698914718104852311252458344736030793138969927
|
||||
chmod 644 /home/prometherion/Documents/clastix/kamaji/deploy/mysql/certs/*
|
||||
secret/mysql-config created
|
||||
secret/kine-secret created
|
||||
serviceaccount/mariadb created
|
||||
service/mariadb created
|
||||
deployment.apps/mariadb created
|
||||
persistentvolumeclaim/pvc-mariadb created
|
||||
```
|
||||
|
||||
## Certificate creation
|
||||
|
||||
```bash
|
||||
$ make mariadb-certificates
|
||||
```
|
||||
|
||||
Communication between kine and the backend is encrypted, therefore, a CA and a certificate from it must be created.
|
||||
|
||||
## Secret Deployment
|
||||
|
||||
```bash
|
||||
$ make mariadb-secrets
|
||||
```
|
||||
|
||||
Previous certificates and MySQL configuration have to be available in order to be used.
|
||||
They will be under the secret `kamaji-system:mysql-config`, used by the MySQL/MariaDB instance.
|
||||
|
||||
## Deployment
|
||||
|
||||
```bash
|
||||
$ make mariadb-deployment
|
||||
```
|
||||
|
||||
Finally, starts the MySQL/MariaDB installation with all the required settings, such as SSL connection, and configuration.
|
||||
|
||||
# Cleanup
|
||||
|
||||
```bash
|
||||
$ make mariadb-destroy
|
||||
```
|
||||
@@ -7,6 +7,7 @@ cnpg-setup:
|
||||
|
||||
cnpg-deploy:
|
||||
@kubectl -n cnpg-system rollout status deployment/cnpg-controller-manager
|
||||
@kubectl create namespace kamaji-system --dry-run=client -o yaml | kubectl apply -f -
|
||||
@kubectl -n kamaji-system apply -f postgresql.yaml
|
||||
@while ! kubectl -n kamaji-system get secret postgresql-superuser > /dev/null 2>&1; do sleep 1; done
|
||||
|
||||
@@ -17,11 +18,10 @@ cnpg:
|
||||
sh -s -- -b $(shell git rev-parse --show-toplevel)/bin
|
||||
|
||||
postgresql-secret: cnpg
|
||||
@kubectl -n kamaji-system get secret postgres-root-cert > /dev/null 2>&1 || $(CNPG) certificate postgres-root-cert \
|
||||
@kubectl -n kamaji-system get secret postgres-root-cert > /dev/null 2>&1 || $(CNPG) -n kamaji-system certificate postgres-root-cert \
|
||||
--cnpg-cluster postgresql \
|
||||
--cnpg-user $$(kubectl -n kamaji-system get secret postgresql-superuser -o jsonpath='{.data.username}' | base64 -d)
|
||||
|
||||
postgresql-destroy:
|
||||
@kubectl delete -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/main/releases/cnpg-1.16.0.yaml --ignore-not-found && \
|
||||
kubectl delete secret postgres-root-cert --ignore-not-found && \
|
||||
kubectl delete secret kine-secret --ignore-not-found
|
||||
kubectl -n kamaji-system delete secret postgres-root-cert --ignore-not-found
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
# PostgreSQL as Kubernetes Storage
|
||||
|
||||
Kamaji offers the possibility of having a different storage system than `etcd` thanks to [kine](https://github.com/k3s-io/kine).
|
||||
One of the implementations is [PostgreSQL](https://www.postgresql.org/).
|
||||
|
||||
Kamaji project is developed using [kind](https://kind.sigs.k8s.io), therefore, a PostgreSQL instance must be deployed in advance into the local kubernetes cluster in order to be used as storage for the tenants.
|
||||
For the sake of simplicity, the [cloudnative-pg](https://cloudnative-pg.io/) Operator will be used to simplify the setup of it.
|
||||
|
||||
There is a Makefile to help with the process:
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
$ make postgresql
|
||||
```
|
||||
|
||||
This target will install the `cloudnative-pg`, creating the PostgreSQL instance in the Kamaji Namespace, along with the generation of the required Secret resource for the kine integration.
|
||||
|
||||
This action is idempotent and doesn't overwrite values if they already exist.
|
||||
|
||||
```shell
|
||||
namespace/cnpg-system unchanged
|
||||
customresourcedefinition.apiextensions.k8s.io/backups.postgresql.cnpg.io configured
|
||||
customresourcedefinition.apiextensions.k8s.io/clusters.postgresql.cnpg.io configured
|
||||
customresourcedefinition.apiextensions.k8s.io/poolers.postgresql.cnpg.io configured
|
||||
customresourcedefinition.apiextensions.k8s.io/scheduledbackups.postgresql.cnpg.io configured
|
||||
serviceaccount/cnpg-manager unchanged
|
||||
clusterrole.rbac.authorization.k8s.io/cnpg-manager configured
|
||||
clusterrolebinding.rbac.authorization.k8s.io/cnpg-manager-rolebinding unchanged
|
||||
configmap/cnpg-default-monitoring unchanged
|
||||
service/cnpg-webhook-service unchanged
|
||||
deployment.apps/cnpg-controller-manager unchanged
|
||||
mutatingwebhookconfiguration.admissionregistration.k8s.io/cnpg-mutating-webhook-configuration configured
|
||||
validatingwebhookconfiguration.admissionregistration.k8s.io/cnpg-validating-webhook-configuration configured
|
||||
deployment "cnpg-controller-manager" successfully rolled out
|
||||
cluster.postgresql.cnpg.io/postgresql unchanged
|
||||
secret/postgres-root-cert created
|
||||
```
|
||||
|
||||
## Operator setup
|
||||
|
||||
```bash
|
||||
$ make cnpg-setup
|
||||
```
|
||||
|
||||
This target will apply all the required manifests with the `cloudnative-pg` CRD, and required RBAC, and Deployment.
|
||||
|
||||
Release [v1.16.0](https://github.com/cloudnative-pg/cloudnative-pg/releases/tag/v1.16.0) has been tested successfully.
|
||||
|
||||
## SSL certificate Secret generation
|
||||
|
||||
```bash
|
||||
$ make postgresql-secret
|
||||
```
|
||||
|
||||
This target will download locally the `kubectl-cnpg` utility to generate an SSL certificate required to secure the connection to the PostgreSQL instance.
|
||||
|
||||
## Certificate generation
|
||||
|
||||
```bash
|
||||
$ make postgresql-secret
|
||||
```
|
||||
|
||||
Generate the Certificate required to connect to the DataStore.
|
||||
|
||||
## Teardown
|
||||
|
||||
```bash
|
||||
$ make postgresql-destroy
|
||||
```
|
||||
|
||||
This will lead to the deletion of the `cloudnative-pg` Operator, along with any instance, and related secrets.
|
||||
|
||||
This action is idempotent.
|
||||
@@ -28,5 +28,5 @@ runcmd:
|
||||
- sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
|
||||
- echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
|
||||
- sudo apt update
|
||||
- sudo apt install -y kubelet=1.23.5-00 kubeadm=1.23.5-00 kubectl=1.23.5-00
|
||||
- sudo apt-mark hold kubelet kubeadm kubectl
|
||||
- sudo apt install -y kubelet=1.25.0-00 kubeadm=1.25.0-00 kubectl=1.25.0-00
|
||||
- sudo apt-mark hold kubelet kubeadm kubectl containerd
|
||||
@@ -1,13 +0,0 @@
|
||||
# Kamaji documentation
|
||||
|
||||
- [Core Concepts](./concepts.md)
|
||||
- [Architecture](./architecture.md)
|
||||
- [Getting started](./getting-started-with-kamaji.md)
|
||||
- Guides:
|
||||
- [Deploy Kamaji](./kamaji-deployment-guide.md)
|
||||
- [Deploy Kamaji on Azure](./kamaji-azure-deployment-guide.md)
|
||||
- Deploy Kamaji on AWS
|
||||
- Deploy Kamaji on GCP
|
||||
- Deploy Kamaji on OpenStack
|
||||
- [Reference](./reference.md)
|
||||
- [Versioning](./versioning.md)
|
||||
@@ -1 +0,0 @@
|
||||
# Kamaji architecture
|
||||
@@ -1,31 +0,0 @@
|
||||
# Core Concepts
|
||||
|
||||
Kamaji is a Kubernetes Operator. It turns any Kubernetes cluster into an _“admin cluster”_ to orchestrate other Kubernetes clusters called _“tenant clusters”_.
|
||||
|
||||
## Tenant Control Plane
|
||||
What makes Kamaji special is that the Control Plane of a _“tenant cluster”_ is just one or more regular pods running in a namespace of the _“admin cluster”_ instead of a dedicated set of Virtual Machines. This solution makes running control planes at scale cheaper and easier to deploy and operate. The Tenant Control Plane components are packaged in the same way they are running in bare metal or virtual nodes. We leverage the `kubeadm` code to set up the control plane components as they were running on their own server. The unchanged images of upstream `kube-apiserver`, `kube-scheduler`, and `kube-controller-manager` are used.
|
||||
|
||||
High Availability and rolling updates of the Tenant Control Plane pods are provided by a regular Deployment. Autoscaling based on the metrics is available. A Service is used to espose the Tenant Control Plane outside of the _“admin cluster”_. The `LoadBalancer` service type is used, `NodePort` and `ClusterIP` with an Ingress Controller are still viable options, depending on the case.
|
||||
|
||||
Kamaji offers a [Custom Resource Definition](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/) to provide a declarative approach of managing a Tenant Control Plane. This *CRD* is called `TenantControlPlane`, or `tcp` in short.
|
||||
|
||||
## Tenant worker nodes
|
||||
And what about the tenant worker nodes? They are just _"worker nodes"_, i.e. regular virtual or bare metal machines, connecting to the APIs server of the Tenant Control Plane. Kamaji's goal is to manage the lifecycle of hundreds of these _“tenant clusters”_, not only one, so how to add another tenant cluster to Kamaji? As you could expect, you have just deploys a new Tenant Control Plane in one of the _“admin cluster”_ namespace, and then joins the tenant worker nodes to it.
|
||||
|
||||
All the tenant clusters built with Kamaji are fully compliant CNCF Kubernetes clusters and are compatible with the standard Kubernetes toolchains everybody knows and loves.
|
||||
|
||||
## Save the state
|
||||
Putting the Tenant Control Plane in a pod is the easiest part. Also, we have to make sure each tenant cluster saves the state to be able to store and retrieve data. A dedicated `etcd` cluster for each tenant cluster doesn’t scale well for a managed service because `etcd` data persistence can be cumbersome at scale, rising the operational effort to mitigate it. So we have to find an alternative keeping in mind our goal for a resilient and cost-optimized solution at the same time. As we can deploy any Kubernetes cluster with an external `etcd` cluster, we explored this option for the tenant control planes. On the admin cluster, we deploy a multi-tenant `etcd` cluster storing the state of multiple tenant clusters.
|
||||
|
||||
With this solution, the resiliency is guaranteed by the usual `etcd` mechanism, and the pods' count remains under control, so it solves the main goal of resiliency and costs optimization. The trade-off here is that we have to operate an external `etcd` cluster, in addition to `etcd` of the _“admin cluster”_ and manage the access to be sure that each _“tenant cluster”_ uses only its data. Also, there are limits in size in `etcd`, defaulted to 2GB and configurable to a maximum of 8GB. We’re solving this issue by pooling multiple `etcd` togheter and sharding the Tenant Control Planes.
|
||||
|
||||
Optionally, Kamaji offers the possibility of using a different storage system than `etcd` to save the state of the tenants' clusters, like MySQL or PostgreSQL compatible databases, thanks to the [kine](https://github.com/k3s-io/kine) integration.
|
||||
|
||||
## Requirements of design
|
||||
These are requirements of design behind Kamaji:
|
||||
|
||||
- Communication between the _“admin cluster”_ and a _“tenant cluster”_ is unidirectional. The _“admin cluster”_ manages a _“tenant cluster”_, but a _“tenant cluster”_ has no awareness of the _“admin cluster”_.
|
||||
- Communication between different _“tenant clusters”_ is not allowed.
|
||||
- The worker nodes of tenant should not run anything beyond tenant's workloads.
|
||||
|
||||
Goals and scope may vary as the project evolves.
|
||||
47
docs/content/concepts.md
Normal file
47
docs/content/concepts.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Concepts
|
||||
|
||||
Kamaji is a Kubernetes Operator. It turns any Kubernetes cluster into an _“admin cluster”_ to orchestrate other Kubernetes clusters called _“tenant clusters”_.
|
||||
|
||||
These are requirements of the design behind Kamaji:
|
||||
|
||||
- Communication between the _“admin cluster”_ and a _“tenant cluster”_ is unidirectional. The _“admin cluster”_ manages a _“tenant cluster”_, but a _“tenant cluster”_ has no awareness of the _“admin cluster”_.
|
||||
- Communication between different _“tenant clusters”_ is not allowed.
|
||||
- The worker nodes of tenant should not run anything beyond tenant's workloads.
|
||||
|
||||
Goals and scope may vary as the project evolves.
|
||||
|
||||
## Tenant Control Plane
|
||||
What makes Kamaji special is that the Control Plane of a _“tenant cluster”_ is just one or more regular pods running in a namespace of the _“admin cluster”_ instead of a dedicated set of Virtual Machines. This solution makes running control planes at scale cheaper and easier to deploy and operate. The Tenant Control Plane components are packaged in the same way they are running in bare metal or virtual nodes. We leverage the `kubeadm` code to set up the control plane components as they were running on their own server. The unchanged images of upstream `kube-apiserver`, `kube-scheduler`, and `kube-controller-manager` are used.
|
||||
|
||||
High Availability and rolling updates of the Tenant Control Plane pods are provided by a regular Deployment. Autoscaling based on the metrics is available. A Service is used to espose the Tenant Control Plane outside of the _“admin cluster”_. The `LoadBalancer` service type is used, `NodePort` and `ClusterIP` with an Ingress Controller are still viable options, depending on the case.
|
||||
|
||||
Kamaji offers a [Custom Resource Definition](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/) to provide a declarative approach of managing a Tenant Control Plane. This *CRD* is called `TenantControlPlane`, or `tcp` in short.
|
||||
|
||||
All the _“tenant clusters”_ built with Kamaji are fully compliant CNCF Kubernetes clusters and are compatible with the standard Kubernetes toolchains everybody knows and loves. See [CNCF compliance](reference/conformance.md).
|
||||
|
||||
## Tenant worker nodes
|
||||
And what about the tenant worker nodes? They are just _"worker nodes"_, i.e. regular virtual or bare metal machines, connecting to the APIs server of the Tenant Control Plane. Kamaji's goal is to manage the lifecycle of hundreds of these _“tenant clusters”_, not only one, so how to add another tenant cluster to Kamaji? As you could expect, you have just deploys a new Tenant Control Plane in one of the _“admin cluster”_ namespace, and then joins the tenant worker nodes to it.
|
||||
|
||||
We have in roadmap, the Cluster APIs support as well as a Terraform provider so that you can create _“tenant clusters”_ in a declarative way.
|
||||
|
||||
## Datastores
|
||||
Putting the Tenant Control Plane in a pod is the easiest part. Also, we have to make sure each tenant cluster saves the state to be able to store and retrieve data. A dedicated `etcd` cluster for each tenant cluster doesn’t scale well for a managed service because `etcd` data persistence can be cumbersome at scale, rising the operational effort to mitigate it. So we have to find an alternative keeping in mind our goal for a resilient and cost-optimized solution at the same time.
|
||||
|
||||
As we can deploy any Kubernetes cluster with an external `etcd` cluster, we explored this option for the tenant control planes. On the admin cluster, we can deploy a multi-tenant `etcd` datastore to save the state of multiple tenant clusters. Kamaji offers a Custom Resource Definition called `DataStore` to provide a declarative approach of managing Tenant datastores. With this solution, the resiliency is guaranteed by the usual `etcd` mechanism, and the pods' count remains under control, so it solves the main goal of resiliency and costs optimization. The trade-off here is that we have to operate an external datastore, in addition to `etcd` of the _“admin cluster”_ and manage the access to be sure that each _“tenant cluster”_ uses only its data.
|
||||
|
||||
### Other storage drivers
|
||||
Kamaji offers the option of using a more capable datastore than `etcd` to save the state of multiple tenants' clusters. Thanks to the native [kine](https://github.com/k3s-io/kine) integration, you can run _MySQL_ or _PostgreSQL_ compatible databases as datastore for _“tenant clusters”_.
|
||||
|
||||
### Pooling
|
||||
By default, Kamaji is expecting to persist all the _“tenant clusters”_ data in a unique datastore that could be backed by different drivers. However, you can pick a different datastore for a specific set of _“tenant clusters”_ that could have different resources assigned or a different tiering. Pooling of multiple datastore is an option you can leverage for a very large set of _“tenant clusters”_ so you can distribute the load properly. As future improvements, we have a _datastore scheduler_ feature in roadmap so that Kamaji itself can assign automatically a _“tenant cluster”_ to the best datastore in the pool.
|
||||
|
||||
## Konnectivity
|
||||
|
||||
In addition to the standard control plane containers, Kamaji creates an instance of [konnectivity-server](https://kubernetes.io/docs/concepts/architecture/control-plane-node-communication/) running as sidecar container in the `tcp` pod and exposed on port `8132` of the `tcp` service.
|
||||
|
||||
This is required when the tenant worker nodes are not reachable from the `tcp` pods. The Konnectivity service consists of two parts: the Konnectivity server in the tenant control plane pod and the Konnectivity agents running on the tenant worker nodes.
|
||||
|
||||
After worker nodes joined the tenant control plane, the Konnectivity agents initiate connections to the Konnectivity server and maintain the network connections. After enabling the Konnectivity service, all control plane to worker nodes traffic goes through these connections.
|
||||
|
||||
> In Kamaji, Konnectivity is enabled by default and can be disabled when not required.
|
||||
|
||||
22
docs/content/contribute/governance.md
Normal file
22
docs/content/contribute/governance.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Governance
|
||||
|
||||
This document lays out the guidelines under which the Kamaji project will be governed.
|
||||
The goal is to make sure that the roles and responsibilities are well defined and clarify how decisions are made.
|
||||
|
||||
## Roles
|
||||
|
||||
In the context of Kamaji project, we consider the following roles:
|
||||
|
||||
* __Users__: everyone using Kamaji, typically willing to provide feedback by proposing features and/or filing issues.
|
||||
|
||||
* __Contributors__: everyone contributing code, documentation, examples, tests, and participating in feature proposals as well as design discussions.
|
||||
|
||||
* __Maintainers__: are responsible for engaging with and assisting contributors to iterate on the contributions until it reaches acceptable quality. Maintainers can decide whether the contributions can be accepted into the project or rejected.
|
||||
|
||||
## Release Management
|
||||
|
||||
The release process will be governed by Maintainers.
|
||||
|
||||
## Roadmap Planning
|
||||
|
||||
Maintainers will share roadmap and release versions as milestones in GitHub [project's page](https://github.com/clastix/kamaji).
|
||||
83
docs/content/contribute/guidelines.md
Normal file
83
docs/content/contribute/guidelines.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# General
|
||||
|
||||
Thank you for your interest in contributing to Kamaji. Whether it's a bug report, new feature, correction, or additional documentation, we greatly value feedback and contributions from our community.
|
||||
|
||||
Please read through this document before submitting any issues or pull requests to ensure we have all the necessary information to effectively respond to your bug report or contribution.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
|
||||
|
||||
1. You are working against the latest source on the *master* branch.
|
||||
1. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
|
||||
1. You open an issue to discuss any significant work: we would hate for your time to be wasted.
|
||||
|
||||
To send us a pull request, please:
|
||||
|
||||
1. Fork the repository.
|
||||
1. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it
|
||||
will be hard for us to focus on your change.
|
||||
1. Ensure local tests pass.
|
||||
1. Commit to your fork using clear commit messages.
|
||||
1. Send us a pull request, answering any default questions in the pull request interface.
|
||||
1. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
|
||||
|
||||
GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
|
||||
[creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
|
||||
|
||||
Make sure to keep Pull Requests small and functional to make them easier to review, understand, and look up in commit history. This repository uses "Squash and Commit" to keep our history clean and make it easier to revert changes based on PR. Adding the appropriate documentation, unit tests and e2e tests as part of a feature is the responsibility of the
|
||||
feature owner, whether it is done in the same Pull Request or not. All the Pull Requests must refer to an already open issue: this is the first phase to contribute also for informing maintainers about the issue.
|
||||
|
||||
## Commits
|
||||
|
||||
Commit's first line should not exceed 50 columns.
|
||||
|
||||
A commit description is welcomed to explain more the changes: just ensure to put a blank line and an arbitrary number of maximum 72 characters long lines, at most one blank line between them.
|
||||
|
||||
Please, split changes into several and documented small commits: this will help us to perform a better review. Commits must follow the Conventional Commits Specification, a lightweight convention on top of commit messages. It provides an easy set of rules for creating an explicit commit history; which makes it easier to write automated tools on top of. This convention dovetails with Semantic Versioning, by describing the features, fixes, and breaking changes made in commit messages. See [Conventional Commits Specification](https://www.conventionalcommits.org) to learn about Conventional Commits.
|
||||
|
||||
> In case of errors or need of changes to previous commits, fix them squashing to make changes atomic.
|
||||
|
||||
## Code convention
|
||||
|
||||
Kamaji is written in Golang. The changes must follow the Pull Request method where a _GitHub Action_ will
|
||||
check the `golangci-lint`, so ensure your changes respect the coding standard.
|
||||
|
||||
### golint
|
||||
|
||||
You can easily check them issuing the _Make_ recipe `golint`.
|
||||
|
||||
```
|
||||
# make golint
|
||||
golangci-lint run -c .golangci.yml
|
||||
```
|
||||
|
||||
> Enabled linters and related options are defined in the [.golanci.yml file](https://github.com/clastix/Kamaji/blob/master/.golangci.yml)
|
||||
|
||||
## Finding contributions to work on
|
||||
Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the
|
||||
default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted'
|
||||
and 'good first issue' issues are a great place to start.
|
||||
|
||||
## Design Docs
|
||||
|
||||
A contributor proposes a design with a PR on the repository to allow for revisions and discussions. If a design needs to be discussed before formulating a document for it, make use of GitHub Discussions to involve the community on the discussion.
|
||||
|
||||
## GitHub Issues
|
||||
|
||||
GitHub Issues are used to file bugs, work items, and feature requests with actionable items/issues.
|
||||
|
||||
When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
|
||||
|
||||
* A reproducible test case or series of steps
|
||||
* The version of the code being used
|
||||
* Any modifications you've made relevant to the bug
|
||||
* Anything unusual about your environment or deployment
|
||||
|
||||
## Miscellanea
|
||||
|
||||
Please, add a new single line at end of any file as the current coding style.
|
||||
|
||||
## Licensing
|
||||
|
||||
See the [LICENSE](https://github.com/clastix/Kamaji/blob/master/LICENSE) file for our project's licensing. We can ask you to confirm the licensing of your contribution.
|
||||
2
docs/content/contribute/index.md
Normal file
2
docs/content/contribute/index.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# Guidelines
|
||||
Guidelines for community contributions.
|
||||
@@ -1,19 +1,22 @@
|
||||
# Setup a minimal Kamaji for development
|
||||
# Getting started
|
||||
|
||||
This document explains how to deploy a minimal Kamaji setup on [KinD](https://kind.sigs.k8s.io/) for development scopes. Please refer to the [Kamaji documentation](../README.md) for understanding all the terms used in this guide, as for example: `admin cluster` and `tenant control plane`.
|
||||
This document explains how to deploy a minimal Kamaji setup on [KinD](https://kind.sigs.k8s.io/) for development scopes. Please refer to the [Kamaji documentation](concepts.md) for understanding all the terms used in this guide, as for example: `admin cluster`, `tenant cluster`, and `tenant control plane`.
|
||||
|
||||
## Pre-requisites
|
||||
|
||||
We assume you have installed on your workstation:
|
||||
|
||||
- [Docker](https://docs.docker.com/engine/install/)
|
||||
- [Docker](https://docker.com)
|
||||
- [KinD](https://kind.sigs.k8s.io/)
|
||||
- [kubectl](https://kubernetes.io/docs/tasks/tools/)
|
||||
- [kubeadm](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/)
|
||||
- [kubectl@v1.25.0](https://kubernetes.io/docs/tasks/tools/#kubectl)
|
||||
- [kubeadm@v1.25.0](https://kubernetes.io/docs/tasks/tools/#kubeadm)
|
||||
- [jq](https://stedolan.github.io/jq/)
|
||||
- [openssl](https://www.openssl.org/)
|
||||
- [cfssl](https://github.com/cloudflare/cfssl)
|
||||
- [cfssljson](https://github.com/cloudflare/cfssl)
|
||||
- [cfssl/cfssljson](https://github.com/cloudflare/cfssl)
|
||||
|
||||
|
||||
> Starting from Kamaji v0.0.2, `kubectl` and `kubeadm` need to meet at least minimum version to `v1.25.0`:
|
||||
> this is required due to the latest changes addressed from the release Kubernetes 1.25 release regarding the `kubelet-config` ConfigMap required for the node join.
|
||||
|
||||
## Setup Kamaji on KinD
|
||||
|
||||
@@ -21,9 +24,9 @@ The instance of Kamaji is made of a single node hosting:
|
||||
|
||||
- admin control-plane
|
||||
- admin worker
|
||||
- multi-tenant etcd cluster
|
||||
- multi-tenant datastore
|
||||
|
||||
### Standard
|
||||
### Standard installation
|
||||
|
||||
You can install your KinD cluster, ETCD multi-tenant cluster and Kamaji operator with a **single command**:
|
||||
|
||||
@@ -35,29 +38,55 @@ Now you can [create your first `TenantControlPlane`](#deploy-tenant-control-plan
|
||||
|
||||
### Data store-specific
|
||||
|
||||
#### ETCD
|
||||
Kamaji offers the possibility of using a different storage system than `ETCD` for the tenants, like `MySQL` or `PostgreSQL` compatible databases.
|
||||
|
||||
The multi-tenant etcd cluster is deployed as statefulset into the Kamaji node.
|
||||
|
||||
Run `make reqs` to setup Kamaji's requisites on KinD:
|
||||
First, setup a KinD cluster:
|
||||
|
||||
```bash
|
||||
$ make -C deploy/kind reqs
|
||||
$ make -C deploy/kind kind
|
||||
```
|
||||
|
||||
At this moment you will have your KinD up and running and ETCD cluster in multitenant mode.
|
||||
#### ETCD
|
||||
|
||||
Deploy a multi-tenant `ETCD` cluster into the Kamaji node:
|
||||
|
||||
```bash
|
||||
$ make -C deploy/kind etcd-cluster
|
||||
```
|
||||
|
||||
Now you're ready to [install Kamaji operator](#install-kamaji).
|
||||
|
||||
#### Kine
|
||||
#### MySQL
|
||||
|
||||
> The MySQL-compatible cluster provisioning is omitted here.
|
||||
Deploy a MySQL/MariaDB backend into the Kamaji node:
|
||||
|
||||
Kamaji offers the possibility of using a different storage system than `ETCD` for the tenants, like MySQL or PostgreSQL compatible databases.
|
||||
```bash
|
||||
$ make -C deploy/kine/mysql mariadb
|
||||
```
|
||||
|
||||
Read it more in the provided [guide](../deploy/kine/README.md).
|
||||
Adjust the Kamaji install manifest `config/install.yaml` according to the example of a MySQL DataStore `config/samples/kamaji_v1alpha1_datastore_mysql.yaml` and make sure Kamaji uses the proper datastore name:
|
||||
|
||||
Assuming you adjusted the [Kamaji manifest](../config/install.yaml) to connect to Kine and compatible database using the proper driver, you can now install it.
|
||||
```
|
||||
--datastore={.metadata.name}
|
||||
```
|
||||
|
||||
Now you're ready to [install Kamaji operator](#install-kamaji).
|
||||
|
||||
#### PostgreSQL
|
||||
|
||||
Deploy a PostgreSQL backend into the Kamaji node:
|
||||
|
||||
```bash
|
||||
$ make -C deploy/kine/postgresql postgresql
|
||||
```
|
||||
|
||||
Adjust the Kamaji install manifest `config/install.yaml` according to the example of a PostgreSQL DataStore `config/samples/kamaji_v1alpha1_datastore_postgresql.yaml` and make sure Kamaji uses the proper datastore name:
|
||||
|
||||
```
|
||||
--datastore={.metadata.name}
|
||||
```
|
||||
|
||||
Now you're ready to [install Kamaji operator](#install-kamaji).
|
||||
|
||||
### Install Kamaji
|
||||
|
||||
@@ -65,6 +94,8 @@ Assuming you adjusted the [Kamaji manifest](../config/install.yaml) to connect t
|
||||
$ kubectl apply -f config/install.yaml
|
||||
```
|
||||
|
||||
> If you experience some errors during the apply of the manifest as `resource mapping not found ... ensure CRDs are installed first`, just apply it again.
|
||||
|
||||
### Deploy Tenant Control Plane
|
||||
|
||||
Now it is the moment of deploying your first tenant control plane.
|
||||
@@ -147,7 +178,7 @@ $ kubectl create -f https://raw.githubusercontent.com/aojea/kindnet/master/insta
|
||||
### Join worker nodes
|
||||
|
||||
```bash
|
||||
$ make kamaji-kind-worker-join
|
||||
$ make -C deploy/kind kamaji-kind-worker-join
|
||||
```
|
||||
|
||||
> To add more worker nodes, run again the command above.
|
||||
@@ -160,12 +191,12 @@ NAME STATUS ROLES AGE VERSION
|
||||
d2d4b468c9de Ready <none> 44s v1.23.4
|
||||
```
|
||||
|
||||
> For more complex scenarios (exposing port, different version and so on), run `join-node.bash`
|
||||
> For more complex scenarios (exposing port, different version and so on), run `join-node.bash`.
|
||||
|
||||
Tenant control plane provision has been finished in a minimal Kamaji setup based on KinD. Therefore, you could develop, test and make your own experiments with Kamaji.
|
||||
|
||||
## Cleanup
|
||||
|
||||
```bash
|
||||
$ make destroy
|
||||
$ make -C deploy/kind destroy
|
||||
```
|
||||
3
docs/content/guides/index.md
Normal file
3
docs/content/guides/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# How to Guides
|
||||
|
||||
This section of the Kamaji documentation contains pages that show how to do a specific thing, typically by giving a sequence of steps.
|
||||
@@ -1,12 +1,19 @@
|
||||
# Setup Kamaji on Azure
|
||||
This guide will lead you through the process of creating a working Kamaji setup on on MS Azure. It requires:
|
||||
This guide will lead you through the process of creating a working Kamaji setup on on MS Azure.
|
||||
|
||||
- one bootstrap local workstation
|
||||
The material here is relatively dense. We strongly encourage you to dedicate time to walk through these instructions, with a mind to learning. We do NOT provide any "one-click" deployment here. However, once you've understood the components involved it is encouraged that you build suitable, auditable GitOps deployment processes around your final infrastructure.
|
||||
|
||||
The guide requires:
|
||||
|
||||
- one bootstrap workstation
|
||||
- an AKS Kubernetes cluster to run the Admin and Tenant Control Planes
|
||||
- an arbitrary number of Azure virtual machines to host `Tenant`s' workloads
|
||||
|
||||
## Summary
|
||||
|
||||
* [Prepare the bootstrap workspace](#prepare-the-bootstrap-workspace)
|
||||
* [Access Admin cluster](#access-admin-cluster)
|
||||
* [Install DataStore](#install-datastore)
|
||||
* [Install Kamaji controller](#install-kamaji-controller)
|
||||
* [Create Tenant Cluster](#create-tenant-cluster)
|
||||
* [Cleanup](#cleanup)
|
||||
@@ -21,10 +28,10 @@ cd kamaji/deploy
|
||||
|
||||
We assume you have installed on your workstation:
|
||||
|
||||
- [kubectl](https://kubernetes.io/docs/tasks/tools/)
|
||||
- [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl)
|
||||
- [kubeadm](https://kubernetes.io/docs/tasks/tools/#kubeadm)
|
||||
- [helm](https://helm.sh/docs/intro/install/)
|
||||
- [jq](https://stedolan.github.io/jq/)
|
||||
- [openssl](https://www.openssl.org/)
|
||||
- [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli)
|
||||
|
||||
Make sure you have a valid Azure subscription, and login to Azure:
|
||||
@@ -47,20 +54,37 @@ az group create \
|
||||
--name $KAMAJI_RG \
|
||||
--location $KAMAJI_REGION
|
||||
|
||||
az network vnet create \
|
||||
--resource-group $KAMAJI_RG \
|
||||
--name $KAMAJI_VNET_NAME \
|
||||
--location $KAMAJI_REGION \
|
||||
--address-prefix $KAMAJI_VNET_ADDRESS
|
||||
|
||||
az network vnet subnet create \
|
||||
--resource-group $KAMAJI_RG \
|
||||
--name $KAMAJI_SUBNET_NAME \
|
||||
--vnet-name $KAMAJI_VNET_NAME \
|
||||
--address-prefixes $KAMAJI_SUBNET_ADDRESS
|
||||
|
||||
KAMAJI_SUBNET_ID=$(az network vnet subnet show \
|
||||
--resource-group ${KAMAJI_RG} \
|
||||
--vnet-name ${KAMAJI_VNET_NAME} \
|
||||
--name ${KAMAJI_SUBNET_NAME} \
|
||||
--query id --output tsv)
|
||||
|
||||
az aks create \
|
||||
--resource-group $KAMAJI_RG \
|
||||
--name $KAMAJI_CLUSTER \
|
||||
--location $KAMAJI_REGION \
|
||||
--vnet-subnet-id $KAMAJI_SUBNET_ID \
|
||||
--zones 1 2 3 \
|
||||
--node-count 3 \
|
||||
--nodepool-name $KAMAJI_CLUSTER \
|
||||
--ssh-key-value @~/.ssh/id_rsa.pub \
|
||||
--no-wait
|
||||
--nodepool-name $KAMAJI_CLUSTER
|
||||
```
|
||||
|
||||
Once the cluster formation succedes, get credentials to access the cluster as admin
|
||||
|
||||
```
|
||||
```bash
|
||||
az aks get-credentials \
|
||||
--resource-group $KAMAJI_RG \
|
||||
--name $KAMAJI_CLUSTER
|
||||
@@ -68,41 +92,41 @@ az aks get-credentials \
|
||||
|
||||
And check you can access:
|
||||
|
||||
```
|
||||
```bash
|
||||
kubectl cluster-info
|
||||
```
|
||||
|
||||
## Install Kamaji
|
||||
There are multiple ways to deploy Kamaji, including a [single YAML file](../config/install.yaml) and [Helm Chart](../charts/kamaji).
|
||||
## Install datastore
|
||||
The Kamaji controller needs to access a multi-tenant datastore in order to save data of the tenants' clusters. The Kamaji Helm Chart provides the installation of an unamanaged `etcd`. However, a managed `etcd` is highly recommended in production.
|
||||
|
||||
### Multi-tenant datastore
|
||||
The Kamaji controller needs to access a multi-tenant datastore in order to save data of the tenants' clusters.
|
||||
Install a multi-tenant `etcd` in the admin cluster as three replicas StatefulSet with data persistence.
|
||||
The Helm [Chart](../charts/kamaji/) provides the installation of an internal `etcd`.
|
||||
However, an externally managed `etcd` is highly recommended.
|
||||
If you'd like to use an external one, you can specify the overrides by setting the value `etcd.deploy=false`.
|
||||
|
||||
Optionally, Kamaji offers the possibility of using a different storage system than `etcd` for the tenants' clusters, like MySQL or PostgreSQL compatible database, thanks to the [kine](https://github.com/k3s-io/kine) integration documented [here](../deploy/kine/README.md).
|
||||
|
||||
### Install with Helm Chart
|
||||
Install with the `helm` in a dedicated namespace of the Admin cluster:
|
||||
As alternative, the [kamaji-etcd](https://github.com/clastix/kamaji-etcd) project provides a viable option to setup a manged multi-tenant `etcd` as 3 replicas StatefulSet with data persistence:
|
||||
|
||||
```bash
|
||||
helm install --create-namespace --namespace kamaji-system kamaji clastix/kamaji
|
||||
helm repo add clastix https://clastix.github.io/charts
|
||||
helm repo update
|
||||
helm install etcd clastix/kamaji-etcd -n kamaji-system --create-namespace
|
||||
```
|
||||
|
||||
The Kamaji controller and the multi-tenant `etcd` are now running:
|
||||
Optionally, Kamaji offers the possibility of using a different storage system for the tenants' clusters, as MySQL or PostgreSQL compatible database, thanks to the native [kine](https://github.com/k3s-io/kine) integration.
|
||||
|
||||
## Install Kamaji Controller
|
||||
Install Kamaji with `helm` using an unmanaged `etcd` as datastore:
|
||||
|
||||
```bash
|
||||
kubectl -n kamaji-system get pods
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
etcd-0 1/1 Running 0 120m
|
||||
etcd-1 1/1 Running 0 120m
|
||||
etcd-2 1/1 Running 0 119m
|
||||
kamaji-857fcdf599-4fb2p 2/2 Running 0 120m
|
||||
helm repo add clastix https://clastix.github.io/charts
|
||||
helm repo update
|
||||
helm install kamaji clastix/kamaji -n kamaji-system --create-namespace
|
||||
```
|
||||
|
||||
You just turned your AKS cluster into a Kamaji cluster to run multiple Tenant Control Planes.
|
||||
Alternatively, if you opted for a managed `etcd` datastore:
|
||||
|
||||
```
|
||||
helm repo add clastix https://clastix.github.io/charts
|
||||
helm repo update
|
||||
helm install kamaji clastix/kamaji -n kamaji-system --create-namespace --set etcd.deploy=false
|
||||
```
|
||||
|
||||
Congratulations! You just turned your Azure Kubernetes AKS cluster into a Kamaji cluster capable to run multiple Tenant Control Planes.
|
||||
|
||||
## Create Tenant Cluster
|
||||
|
||||
@@ -135,17 +159,17 @@ spec:
|
||||
resources:
|
||||
apiServer:
|
||||
requests:
|
||||
cpu: 500m
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
limits: {}
|
||||
controllerManager:
|
||||
requests:
|
||||
cpu: 250m
|
||||
cpu: 125m
|
||||
memory: 256Mi
|
||||
limits: {}
|
||||
scheduler:
|
||||
requests:
|
||||
cpu: 250m
|
||||
cpu: 125m
|
||||
memory: 256Mi
|
||||
limits: {}
|
||||
service:
|
||||
@@ -198,31 +222,30 @@ spec:
|
||||
type: LoadBalancer
|
||||
EOF
|
||||
|
||||
kubectl create namespace ${TENANT_NAMESPACE}
|
||||
kubectl apply -f ${TENANT_NAMESPACE}-${TENANT_NAME}-tcp.yaml
|
||||
kubectl -n ${TENANT_NAMESPACE} apply -f ${TENANT_NAMESPACE}-${TENANT_NAME}-tcp.yaml
|
||||
```
|
||||
|
||||
Make sure:
|
||||
|
||||
- the following annotation: `service.beta.kubernetes.io/azure-load-balancer-internal=true` is set on the `tcp` service. It tells Azure to expose the service within an internal loadbalancer.
|
||||
|
||||
- the following annotation: `service.beta.kubernetes.io/azure-dns-label-name=${TENANT_NAME}` is set the public loadbalancer service. It tells Azure to expose the Tenant Control Plane with domain name: `${TENANT_NAME}.${TENANT_DOMAIN}`.
|
||||
- the following annotation: `service.beta.kubernetes.io/azure-dns-label-name=${TENANT_NAME}` is set the public loadbalancer service. It tells Azure to expose the Tenant Control Plane with public domain name: `${TENANT_NAME}.${TENANT_DOMAIN}`.
|
||||
|
||||
### Working with Tenant Control Plane
|
||||
|
||||
Check the access to the Tenant Control Plane:
|
||||
|
||||
```
|
||||
```bash
|
||||
curl -k https://${TENANT_NAME}.${KAMAJI_REGION}.cloudapp.azure.com/healthz
|
||||
curl -k https://${TENANT_NAME}.${KAMAJI_REGION}.cloudapp.azure.com/version
|
||||
```
|
||||
|
||||
Let's retrieve the `kubeconfig` in order to work with it:
|
||||
|
||||
```
|
||||
```bash
|
||||
kubectl get secrets -n ${TENANT_NAMESPACE} ${TENANT_NAME}-admin-kubeconfig -o json \
|
||||
| jq -r '.data["admin.conf"]' \
|
||||
| base64 -d \
|
||||
| base64 --decode \
|
||||
> ${TENANT_NAMESPACE}-${TENANT_NAME}.kubeconfig
|
||||
|
||||
kubectl --kubeconfig=${TENANT_NAMESPACE}-${TENANT_NAME}.kubeconfig config \
|
||||
@@ -248,118 +271,65 @@ NAME ENDPOINTS AGE
|
||||
kubernetes 10.240.0.100:6443 57m
|
||||
```
|
||||
|
||||
### Prepare the Infrastructure for the Tenant virtual machines
|
||||
Kamaji provides Control Plane as a Service, so the tenant user can join his own virtual machines as worker nodes. Each tenant can place his virtual machines in a dedicated Azure virtual network.
|
||||
### Prepare worker nodes to join
|
||||
Currently Kamaji does not provide any helper for creation of tenant worker nodes. You should get a set of machines from your infrastructure provider, turn them into worker nodes, and then join to the tenant control plane with the `kubeadm`. In the future, we'll provide integration with Cluster APIs and other tools, as for example, Terrform.
|
||||
|
||||
Prepare the Tenant infrastructure:
|
||||
|
||||
```
|
||||
az group create \
|
||||
--name $TENANT_RG \
|
||||
--location $KAMAJI_REGION
|
||||
|
||||
az network nsg create \
|
||||
--resource-group $TENANT_RG \
|
||||
--name $TENANT_NSG
|
||||
|
||||
az network nsg rule create \
|
||||
--resource-group $TENANT_RG \
|
||||
--nsg-name $TENANT_NSG \
|
||||
--name $TENANT_NSG-ssh \
|
||||
--protocol tcp \
|
||||
--priority 1000 \
|
||||
--destination-port-range 22 \
|
||||
--access allow
|
||||
|
||||
az network vnet create \
|
||||
--resource-group $TENANT_RG \
|
||||
--name $TENANT_VNET_NAME \
|
||||
--address-prefix $TENANT_VNET_ADDRESS \
|
||||
--subnet-name $TENANT_SUBNET_NAME \
|
||||
--subnet-prefix $TENANT_SUBNET_ADDRESS
|
||||
|
||||
az network vnet subnet create \
|
||||
--resource-group $TENANT_RG \
|
||||
--vnet-name $TENANT_VNET_NAME \
|
||||
--name $TENANT_SUBNET_NAME \
|
||||
--address-prefixes $TENANT_SUBNET_ADDRESS \
|
||||
--network-security-group $TENANT_NSG
|
||||
```
|
||||
|
||||
Connection between the Tenant virtual network and the Kamaji AKS virtual network leverages on the [Azure Network Peering](https://docs.microsoft.com/en-us/azure/virtual-network/virtual-network-peering-overview).
|
||||
|
||||
Enable the network peering between the Tenant Virtual Network and the Kamaji AKS Virtual Network:
|
||||
Create an Azure VM Stateful Set to host worker nodes
|
||||
|
||||
```bash
|
||||
KAMAJI_VNET_NAME=`az network vnet list -g $KAMAJI_NODE_RG --query [].name --out tsv`
|
||||
KAMAJI_VNET_ID=`az network vnet list -g $KAMAJI_NODE_RG --query [].id --out tsv`
|
||||
TENANT_VNET_ID=`az network vnet list -g $TENANT_RG --query [].id --out tsv`
|
||||
|
||||
az network vnet peering create \
|
||||
--resource-group $TENANT_RG \
|
||||
--name $TENANT_NAME-$KAMAJI_CLUSTER \
|
||||
--vnet-name $TENANT_VNET_NAME \
|
||||
--remote-vnet $KAMAJI_VNET_ID \
|
||||
--allow-vnet-access
|
||||
|
||||
az network vnet peering create \
|
||||
--resource-group $KAMAJI_NODE_RG \
|
||||
--name $KAMAJI_CLUSTER-$TENANT_NAME \
|
||||
az network vnet subnet create \
|
||||
--resource-group $KAMAJI_RG \
|
||||
--name $TENANT_SUBNET_NAME \
|
||||
--vnet-name $KAMAJI_VNET_NAME \
|
||||
--remote-vnet $TENANT_VNET_ID \
|
||||
--allow-vnet-access
|
||||
```
|
||||
--address-prefixes $TENANT_SUBNET_ADDRESS
|
||||
|
||||
[Azure Network Security Groups](https://docs.microsoft.com/en-us/azure/virtual-network/network-security-groups-overview) can be used to control the traffic between the Tenant virtual network and the Kamaji AKS virtual network for a stronger isolation. See the required [ports and protocols](https://kubernetes.io/docs/reference/ports-and-protocols/) between Kubernetes control plane and worker nodes.
|
||||
|
||||
### Create the tenant virtual machines
|
||||
Create an Azure VM Stateful Set to host virtual machines
|
||||
|
||||
```
|
||||
az vmss create \
|
||||
--name $TENANT_VMSS \
|
||||
--resource-group $TENANT_RG \
|
||||
--resource-group $KAMAJI_RG \
|
||||
--image $TENANT_VM_IMAGE \
|
||||
--public-ip-per-vm \
|
||||
--vnet-name $TENANT_VNET_NAME \
|
||||
--vnet-name $KAMAJI_VNET_NAME \
|
||||
--subnet $TENANT_SUBNET_NAME \
|
||||
--ssh-key-value @~/.ssh/id_rsa.pub \
|
||||
--computer-name-prefix $TENANT_NAME- \
|
||||
--nsg $TENANT_NSG \
|
||||
--custom-data ./tenant-cloudinit.yaml \
|
||||
--instance-count 0
|
||||
--load-balancer "" \
|
||||
--instance-count 0
|
||||
|
||||
az vmss update \
|
||||
--resource-group $TENANT_RG \
|
||||
--resource-group $KAMAJI_RG \
|
||||
--name $TENANT_VMSS \
|
||||
--set virtualMachineProfile.networkProfile.networkInterfaceConfigurations[0].enableIPForwarding=true
|
||||
|
||||
az vmss scale \
|
||||
--resource-group $TENANT_RG \
|
||||
--resource-group $KAMAJI_RG \
|
||||
--name $TENANT_VMSS \
|
||||
--new-capacity 3
|
||||
```
|
||||
|
||||
### Join the tenant virtual machines to the tenant control plane
|
||||
The current approach for joining nodes is to use the `kubeadm` one therefore, we will create a bootstrap token to perform the action:
|
||||
### Join worker nodes
|
||||
The current approach for joining nodes is to use `kubeadm` and therefore, we will create a bootstrap token to perform the action. In order to facilitate the step, we will store the entire command of joining in a variable:
|
||||
|
||||
```bash
|
||||
TENANT_ADDR=$(kubectl -n ${TENANT_NAMESPACE} get svc ${TENANT_NAME} -o json | jq -r ."spec.loadBalancerIP")
|
||||
JOIN_CMD=$(echo "sudo kubeadm join ${TENANT_ADDR}:6443 ")$(kubeadm --kubeconfig=${TENANT_NAMESPACE}-${TENANT_NAME}.kubeconfig token create --print-join-command |cut -d" " -f4-)
|
||||
```
|
||||
|
||||
A bash loop will be used to join all the available nodes.
|
||||
|
||||
```bash
|
||||
HOSTS=($(az vmss list-instance-public-ips \
|
||||
--resource-group $TENANT_RG \
|
||||
--name $TENANT_VMSS \
|
||||
--query "[].ipAddress" \
|
||||
--output tsv))
|
||||
VMIDS=($(az vmss list-instances \
|
||||
--resource-group $KAMAJI_RG \
|
||||
--name $TENANT_VMSS \
|
||||
--query [].instanceId \
|
||||
--output tsv))
|
||||
|
||||
for i in ${!HOSTS[@]}; do
|
||||
HOST=${HOSTS[$i]}
|
||||
echo $HOST
|
||||
ssh ${USER}@${HOST} -t ${JOIN_CMD};
|
||||
for i in ${!VMIDS[@]}; do
|
||||
VMID=${VMIDS[$i]}
|
||||
az vmss run-command create \
|
||||
--name join-tenant-control-plane \
|
||||
--vmss-name $TENANT_VMSS \
|
||||
--resource-group $KAMAJI_RG \
|
||||
--instance-id ${VMID} \
|
||||
--script "${JOIN_CMD}"
|
||||
done
|
||||
```
|
||||
|
||||
@@ -369,51 +339,43 @@ Checking the nodes:
|
||||
kubectl --kubeconfig=${TENANT_NAMESPACE}-${TENANT_NAME}.kubeconfig get nodes
|
||||
|
||||
NAME STATUS ROLES AGE VERSION
|
||||
tenant-00-000000 NotReady <none> 112s v1.23.5
|
||||
tenant-00-000002 NotReady <none> 92s v1.23.5
|
||||
tenant-00-000003 NotReady <none> 71s v1.23.5
|
||||
tenant-00-000000 NotReady <none> 112s v1.25.0
|
||||
tenant-00-000002 NotReady <none> 92s v1.25.0
|
||||
tenant-00-000003 NotReady <none> 71s v1.25.0
|
||||
```
|
||||
|
||||
The cluster needs a [CNI](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/) plugin to get the nodes ready. In our case, we are going to install [calico](https://projectcalico.docs.tigera.io/about/about-calico).
|
||||
The cluster needs a [CNI](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/) plugin to get the nodes ready. In this guide, we are going to install [calico](https://projectcalico.docs.tigera.io/about/about-calico), but feel free to use one of your taste.
|
||||
|
||||
Download the latest stable Calico manifest:
|
||||
|
||||
```bash
|
||||
kubectl apply -f calico-cni/calico-crd.yaml --kubeconfig=${TENANT_NAMESPACE}-${TENANT_NAME}.kubeconfig
|
||||
kubectl apply -f calico-cni/calico-azure.yaml --kubeconfig=${TENANT_NAMESPACE}-${TENANT_NAME}.kubeconfig
|
||||
curl https://raw.githubusercontent.com/projectcalico/calico/v3.24.1/manifests/calico.yaml -O
|
||||
```
|
||||
|
||||
And after a while, `kube-system` pods will be running.
|
||||
As per [documentation](https://projectcalico.docs.tigera.io/reference/public-cloud/azure), Calico in VXLAN mode is supported on Azure while IPIP packets are blocked by the Azure network fabric. Make sure you edit the manifest above and set the following variables:
|
||||
|
||||
- `CLUSTER_TYPE="k8s"`
|
||||
- `CALICO_IPV4POOL_IPIP="Never"`
|
||||
- `CALICO_IPV4POOL_VXLAN="Always"`
|
||||
|
||||
Apply to the tenant cluster:
|
||||
|
||||
```bash
|
||||
kubectl --kubeconfig=${TENANT_NAMESPACE}-${TENANT_NAME}.kubeconfig get po -n kube-system
|
||||
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
calico-kube-controllers-8594699699-dlhbj 1/1 Running 0 3m
|
||||
calico-node-kxf6n 1/1 Running 0 3m
|
||||
calico-node-qtdlw 1/1 Running 0 3m
|
||||
coredns-64897985d-2v5lc 1/1 Running 0 5m
|
||||
coredns-64897985d-nq276 1/1 Running 0 5m
|
||||
kube-proxy-cwdww 1/1 Running 0 3m
|
||||
kube-proxy-m48v4 1/1 Running 0 3m
|
||||
kubectl --kubeconfig=${TENANT_NAMESPACE}-${TENANT_NAME}.kubeconfig apply -f calico.yaml
|
||||
```
|
||||
|
||||
And the nodes will be ready
|
||||
And after a while, nodes will be ready
|
||||
|
||||
```bash
|
||||
kubectl --kubeconfig=${TENANT_NAMESPACE}-${TENANT_NAME}.kubeconfig get nodes
|
||||
|
||||
NAME STATUS ROLES AGE VERSION
|
||||
tenant-00-000000 Ready <none> 3m38s v1.23.5
|
||||
tenant-00-000002 Ready <none> 3m18s v1.23.5
|
||||
tenant-00-000003 Ready <none> 2m57s v1.23.5
|
||||
tenant-00-000000 Ready <none> 3m38s v1.25.0
|
||||
tenant-00-000002 Ready <none> 3m18s v1.25.0
|
||||
tenant-00-000003 Ready <none> 2m57s v1.25.0
|
||||
```
|
||||
|
||||
## Cleanup
|
||||
To get rid of the Tenant infrastructure, remove the RESOURCE_GROUP:
|
||||
|
||||
```
|
||||
az group delete --name $TENANT_RG --yes --no-wait
|
||||
```
|
||||
|
||||
To get rid of the Kamaji infrastructure, remove the RESOURCE_GROUP:
|
||||
|
||||
```
|
||||
@@ -1,14 +1,19 @@
|
||||
# Setup Kamaji
|
||||
This guide will lead you through the process of creating a working Kamaji setup on a generic Kubernetes cluster. It requires:
|
||||
# Setup Kamaji on a generic infrastructure
|
||||
This guide will lead you through the process of creating a working Kamaji setup on a generic infrastructure, either virtual or bare metal.
|
||||
|
||||
- one bootstrap local workstation
|
||||
- a Kubernetes cluster 1.22+, to run the Admin and Tenant Control Planes
|
||||
- an arbitrary number of machines to host Tenants' workloads
|
||||
The material here is relatively dense. We strongly encourage you to dedicate time to walk through these instructions, with a mind to learning. We do NOT provide any "one-click" deployment here. However, once you've understood the components involved it is encouraged that you build suitable, auditable GitOps deployment processes around your final infrastructure.
|
||||
|
||||
> In this guide, we assume the machines are running `Ubuntu 20.04`.
|
||||
The guide requires:
|
||||
|
||||
- one bootstrap workstation
|
||||
- a Kubernetes cluster to run the Admin and Tenant Control Planes
|
||||
- an arbitrary number of machines to host `Tenant`s' workloads
|
||||
|
||||
## Summary
|
||||
|
||||
* [Prepare the bootstrap workspace](#prepare-the-bootstrap-workspace)
|
||||
* [Access Admin cluster](#access-admin-cluster)
|
||||
* [Install DataStore](#install-datastore)
|
||||
* [Install Kamaji controller](#install-kamaji-controller)
|
||||
* [Create Tenant Cluster](#create-tenant-cluster)
|
||||
* [Cleanup](#cleanup)
|
||||
@@ -23,10 +28,10 @@ cd kamaji/deploy
|
||||
|
||||
We assume you have installed on your workstation:
|
||||
|
||||
- [kubectl](https://kubernetes.io/docs/tasks/tools/)
|
||||
- [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl)
|
||||
- [kubeadm](https://kubernetes.io/docs/tasks/tools/#kubeadm)
|
||||
- [helm](https://helm.sh/docs/intro/install/)
|
||||
- [jq](https://stedolan.github.io/jq/)
|
||||
- [openssl](https://www.openssl.org/)
|
||||
|
||||
## Access Admin cluster
|
||||
In Kamaji, an Admin Cluster is a regular Kubernetes cluster which hosts zero to many Tenant Cluster Control Planes. The admin cluster acts as management cluster for all the Tenant clusters and implements Monitoring, Logging, and Governance of all the Kamaji setup, including all Tenant clusters.
|
||||
@@ -37,42 +42,46 @@ Throughout the following instructions, shell variables are used to indicate valu
|
||||
source kamaji.env
|
||||
```
|
||||
|
||||
Any regular and conformant Kubernetes v1.22+ cluster can be turned into a Kamaji setup. To work properly, the admin cluster should provide:
|
||||
Any regular and conformant Kubernetes v1.22+ cluster can be turned into a Kamaji setup. To work properly, the admin cluster should provide at least:
|
||||
|
||||
- CNI module installed, eg. [Calico](https://github.com/projectcalico/calico), [Cilium](https://github.com/cilium/cilium).
|
||||
- CSI module installed with a Storage Class for the Tenants' `etcd`.
|
||||
- CSI module installed with a Storage Class for the Tenants' `etcd`. Local Persistent Volumes are an option.
|
||||
- Support for LoadBalancer Service Type, or alternatively, an Ingress Controller, eg. [ingress-nginx](https://github.com/kubernetes/ingress-nginx), [haproxy](https://github.com/haproxytech/kubernetes-ingress).
|
||||
- Monitoring Stack, eg. [Prometheus](https://github.com/prometheus-community).
|
||||
|
||||
Make sure you have a `kubeconfig` file with admin permissions on the cluster you want to turn into Kamaji Admin Cluster.
|
||||
|
||||
## Install Kamaji
|
||||
There are multiple ways to deploy Kamaji, including a [single YAML file](../config/install.yaml) and [Helm Chart](../charts/kamaji).
|
||||
## Install datastore
|
||||
The Kamaji controller needs to access a multi-tenant datastore in order to save data of the tenants' clusters. The Kamaji Helm Chart provides the installation of an unamanaged `etcd`. However, a managed `etcd` is highly recommended in production.
|
||||
|
||||
### Multi-tenant datastore
|
||||
The Kamaji controller needs to access a multi-tenant datastore in order to save data of the tenants' clusters. Install a multi-tenant `etcd` in the admin cluster as three replicas StatefulSet with data persistence. The Helm [Chart](../charts/kamaji/) provides the installation of an internal `etcd`. However, an externally managed `etcd` is highly recommended. If you'd like to use an external one, you can specify the overrides by setting the value `etcd.deploy=false`.
|
||||
|
||||
Optionally, Kamaji offers the possibility of using a different storage system than `etcd` for the tenants' clusters, like MySQL compatible database, thanks to the [kine](https://github.com/k3s-io/kine) integration [here](../deploy/kine/mysql/README.md).
|
||||
|
||||
### Install with Helm Chart
|
||||
Install with the `helm` in a dedicated namespace of the Admin cluster:
|
||||
As alternative, the [kamaji-etcd](https://github.com/clastix/kamaji-etcd) project provides a viable option to setup a manged multi-tenant `etcd` as 3 replicas StatefulSet with data persistence:
|
||||
|
||||
```bash
|
||||
helm install --create-namespace --namespace kamaji-system kamaji clastix/kamaji
|
||||
helm repo add clastix https://clastix.github.io/charts
|
||||
helm repo update
|
||||
helm install etcd clastix/kamaji-etcd -n kamaji-system --create-namespace
|
||||
```
|
||||
|
||||
The Kamaji controller and the multi-tenant `etcd` are now running:
|
||||
Optionally, Kamaji offers the possibility of using a different storage system for the tenants' clusters, as MySQL or PostgreSQL compatible database, thanks to the native [kine](https://github.com/k3s-io/kine) integration.
|
||||
|
||||
## Install Kamaji Controller
|
||||
Install Kamaji with `helm` using an unmanaged `etcd` as datastore:
|
||||
|
||||
```bash
|
||||
kubectl -n kamaji-system get pods
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
etcd-0 1/1 Running 0 120m
|
||||
etcd-1 1/1 Running 0 120m
|
||||
etcd-2 1/1 Running 0 119m
|
||||
kamaji-857fcdf599-4fb2p 2/2 Running 0 120m
|
||||
helm repo add clastix https://clastix.github.io/charts
|
||||
helm repo update
|
||||
helm install kamaji clastix/kamaji -n kamaji-system --create-namespace
|
||||
```
|
||||
|
||||
You just turned your Kubernetes cluster into a Kamaji cluster to run multiple Tenant Control Planes.
|
||||
Alternatively, if you opted for a managed `etcd` datastore:
|
||||
|
||||
```bash
|
||||
helm repo add clastix https://clastix.github.io/charts
|
||||
helm repo update
|
||||
helm install kamaji clastix/kamaji -n kamaji-system --create-namespace --set etcd.deploy=false
|
||||
```
|
||||
|
||||
Congratulations! You just turned your Kubernetes cluster into a Kamaji cluster capable to run multiple Tenant Control Planes.
|
||||
|
||||
## Create Tenant Cluster
|
||||
|
||||
@@ -101,17 +110,17 @@ spec:
|
||||
resources:
|
||||
apiServer:
|
||||
requests:
|
||||
cpu: 500m
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
limits: {}
|
||||
controllerManager:
|
||||
requests:
|
||||
cpu: 250m
|
||||
cpu: 125m
|
||||
memory: 256Mi
|
||||
limits: {}
|
||||
scheduler:
|
||||
requests:
|
||||
cpu: 250m
|
||||
cpu: 125m
|
||||
memory: 256Mi
|
||||
limits: {}
|
||||
service:
|
||||
@@ -146,8 +155,7 @@ spec:
|
||||
limits: {}
|
||||
EOF
|
||||
|
||||
kubectl create namespace ${TENANT_NAMESPACE}
|
||||
kubectl apply -f ${TENANT_NAMESPACE}-${TENANT_NAME}-tcp.yaml
|
||||
kubectl -n ${TENANT_NAMESPACE} apply -f ${TENANT_NAMESPACE}-${TENANT_NAME}-tcp.yaml
|
||||
```
|
||||
|
||||
After a few minutes, check the created resources in the tenants namespace and when ready it will look similar to the following:
|
||||
@@ -171,39 +179,8 @@ service/tenant-00 LoadBalancer 10.32.132.241 192.168.32.240 6443:32152/T
|
||||
|
||||
The regular Tenant Control Plane containers: `kube-apiserver`, `kube-controller-manager`, `kube-scheduler` are running unchanged in the `tcp` pods instead of dedicated machines and they are exposed through a service on the port `6443` of worker nodes in the Admin cluster.
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: tenant-00
|
||||
spec:
|
||||
clusterIP: 10.32.233.177
|
||||
loadBalancerIP: 192.168.32.240
|
||||
ports:
|
||||
- name: kube-apiserver
|
||||
nodePort: 31073
|
||||
port: 6443
|
||||
protocol: TCP
|
||||
targetPort: 6443
|
||||
- name: konnectivity-server
|
||||
nodePort: 32125
|
||||
port: 8132
|
||||
protocol: TCP
|
||||
targetPort: 8132
|
||||
selector:
|
||||
kamaji.clastix.io/soot: tenant-00
|
||||
type: LoadBalancer
|
||||
```
|
||||
|
||||
The `LoadBalancer` service type is used to expose the Tenant Control Plane. However, `NodePort` and `ClusterIP` with an Ingress Controller are still viable options, depending on the case. High Availability and rolling updates of the Tenant Control Plane are provided by the `tcp` Deployment and all the resources reconcilied by the Kamaji controller.
|
||||
|
||||
### Konnectivity
|
||||
In addition to the standard control plane containers, Kamaji creates an instance of [konnectivity-server](https://kubernetes.io/docs/concepts/architecture/control-plane-node-communication/) running as sidecar container in the `tcp` pod and exposed on port `8132` of the `tcp` service.
|
||||
|
||||
This is required when the tenant worker nodes are not reachable from the `tcp` pods. The Konnectivity service consists of two parts: the Konnectivity server in the tenant control plane pod and the Konnectivity agents running on the tenant worker nodes. After worker nodes joined the tenant control plane, the Konnectivity agents initiate connections to the Konnectivity server and maintain the network connections. After enabling the Konnectivity service, all control plane to worker nodes traffic goes through these connections.
|
||||
|
||||
> In Kamaji, Konnectivity is enabled by default and can be disabled when not required.
|
||||
|
||||
### Working with Tenant Control Plane
|
||||
|
||||
Collect the external IP address of the `tcp` service:
|
||||
@@ -224,7 +201,7 @@ The `kubeconfig` required to access the Tenant Control Plane is stored in a secr
|
||||
```bash
|
||||
kubectl get secrets -n ${TENANT_NAMESPACE} ${TENANT_NAME}-admin-kubeconfig -o json \
|
||||
| jq -r '.data["admin.conf"]' \
|
||||
| base64 -d \
|
||||
| base64 --decode \
|
||||
> ${TENANT_NAMESPACE}-${TENANT_NAME}.kubeconfig
|
||||
```
|
||||
|
||||
@@ -255,38 +232,36 @@ kubernetes 192.168.32.240:6443 18m
|
||||
|
||||
And make sure it is `${TENANT_ADDR}:${TENANT_PORT}`.
|
||||
|
||||
### Preparing Worker Nodes to join
|
||||
### Prepare worker nodes to join
|
||||
|
||||
Currently Kamaji does not provide any helper for creation of tenant worker nodes. You should get a set of machines from your infrastructure provider, turn them into worker nodes, and then join to the tenant control plane with the `kubeadm`. In the future, we'll provide integration with Cluster APIs and other IaC tools.
|
||||
Currently Kamaji does not provide any helper for creation of tenant worker nodes. You should get a set of machines from your infrastructure provider, turn them into worker nodes, and then join to the tenant control plane with the `kubeadm`. In the future, we'll provide integration with Cluster APIs and other tools, as for example, Terraform.
|
||||
|
||||
Use bash script `nodes-prerequisites.sh` to install the dependencies on all the worker nodes:
|
||||
You can use the provided helper script `/deploy/nodes-prerequisites.sh`, in order to install the dependencies on all the worker nodes:
|
||||
|
||||
- Install `containerd` as container runtime
|
||||
- Install `crictl`, the command line for working with `containerd`
|
||||
- Install `kubectl`, `kubelet`, and `kubeadm` in the desired version
|
||||
|
||||
> Warning: we assume worker nodes are machines running `Ubuntu 20.04`
|
||||
> Warning: the script assumes all worker nodes are running `Ubuntu 20.04`. Make sure to adapt the script if you're using a different distribution.
|
||||
|
||||
Run the installation script:
|
||||
Run the script:
|
||||
|
||||
```bash
|
||||
HOSTS=(${WORKER0} ${WORKER1} ${WORKER2})
|
||||
./nodes-prerequisites.sh ${TENANT_VERSION:1} ${HOSTS[@]}
|
||||
```
|
||||
|
||||
### Join Command
|
||||
|
||||
The current approach for joining nodes is to use the kubeadm one therefore, we will create a bootstrap token to perform the action. In order to facilitate the step, we will store the entire command of joining in a variable.
|
||||
### Join worker nodes
|
||||
The current approach for joining nodes is to use `kubeadm` and therefore, we will create a bootstrap token to perform the action. In order to facilitate the step, we will store the entire command of joining in a variable:
|
||||
|
||||
```bash
|
||||
JOIN_CMD=$(echo "sudo ")$(kubeadm --kubeconfig=${TENANT_NAMESPACE}-${TENANT_NAME}.kubeconfig token create --print-join-command)
|
||||
```
|
||||
|
||||
### Adding Worker Nodes
|
||||
|
||||
A bash loop will be used to join all the available nodes.
|
||||
|
||||
```bash
|
||||
HOSTS=(${WORKER0} ${WORKER1} ${WORKER2})
|
||||
for i in "${!HOSTS[@]}"; do
|
||||
HOST=${HOSTS[$i]}
|
||||
ssh ${USER}@${HOST} -t ${JOIN_CMD};
|
||||
@@ -299,163 +274,42 @@ Checking the nodes:
|
||||
kubectl --kubeconfig=${TENANT_NAMESPACE}-${TENANT_NAME}.kubeconfig get nodes
|
||||
|
||||
NAME STATUS ROLES AGE VERSION
|
||||
tenant-00-worker-00 NotReady <none> 25s v1.23.5
|
||||
tenant-00-worker-01 NotReady <none> 17s v1.23.5
|
||||
tenant-00-worker-02 NotReady <none> 9s v1.23.5
|
||||
tenant-00-worker-00 NotReady <none> 25s v1.25.0
|
||||
tenant-00-worker-01 NotReady <none> 17s v1.25.0
|
||||
tenant-00-worker-02 NotReady <none> 9s v1.25.0
|
||||
```
|
||||
|
||||
The cluster needs a [CNI](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/) plugin to get the nodes ready. In our case, we are going to install [calico](https://projectcalico.docs.tigera.io/about/about-calico).
|
||||
The cluster needs a [CNI](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/) plugin to get the nodes ready. In this guide, we are going to install [calico](https://projectcalico.docs.tigera.io/about/about-calico), but feel free to use one of your taste.
|
||||
|
||||
Download the latest stable Calico manifest:
|
||||
|
||||
```bash
|
||||
kubectl apply -f calico-cni/calico-crd.yaml --kubeconfig=${TENANT_NAMESPACE}-${TENANT_NAME}.kubeconfig
|
||||
kubectl apply -f calico-cni/calico.yaml --kubeconfig=${TENANT_NAMESPACE}-${TENANT_NAME}.kubeconfig
|
||||
curl https://raw.githubusercontent.com/projectcalico/calico/v3.24.1/manifests/calico.yaml -O
|
||||
```
|
||||
|
||||
And after a while, `kube-system` pods will be running.
|
||||
Before to apply the Calico manifest, you can customize it as necessary according to your preferences.
|
||||
|
||||
Apply to the tenant cluster:
|
||||
|
||||
```bash
|
||||
kubectl --kubeconfig=${TENANT_NAMESPACE}-${TENANT_NAME}.kubeconfig get pods -n kube-system
|
||||
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
calico-kube-controllers-8594699699-dlhbj 1/1 Running 0 3m
|
||||
calico-node-kxf6n 1/1 Running 0 3m
|
||||
calico-node-qtdlw 1/1 Running 0 3m
|
||||
coredns-64897985d-2v5lc 1/1 Running 0 5m
|
||||
coredns-64897985d-nq276 1/1 Running 0 5m
|
||||
kube-proxy-cwdww 1/1 Running 0 3m
|
||||
kube-proxy-m48v4 1/1 Running 0 3m
|
||||
kubectl --kubeconfig=${TENANT_NAMESPACE}-${TENANT_NAME}.kubeconfig apply -f calico.yaml
|
||||
```
|
||||
|
||||
And the nodes will be ready
|
||||
And after a while, nodes will be ready
|
||||
|
||||
```bash
|
||||
kubectl get nodes --kubeconfig=${TENANT_NAMESPACE}-${TENANT_NAME}.kubeconfig
|
||||
kubectl --kubeconfig=${TENANT_NAMESPACE}-${TENANT_NAME}.kubeconfig get nodes
|
||||
NAME STATUS ROLES AGE VERSION
|
||||
tenant-00-worker-00 Ready <none> 2m48s v1.23.5
|
||||
tenant-00-worker-01 Ready <none> 2m40s v1.23.5
|
||||
tenant-00-worker-02 Ready <none> 2m32s v1.23.5
|
||||
```
|
||||
|
||||
## Smoke test
|
||||
|
||||
The tenant cluster is now ready to accept workloads.
|
||||
|
||||
Export its `kubeconfig` file
|
||||
|
||||
```bash
|
||||
export KUBECONFIG=${TENANT_NAMESPACE}-${TENANT_NAME}.kubeconfig
|
||||
```
|
||||
|
||||
#### Deployment
|
||||
Deploy a `nginx` application on the tenant cluster
|
||||
|
||||
```bash
|
||||
kubectl create deployment nginx --image=nginx
|
||||
```
|
||||
|
||||
and check the `nginx` pod gets scheduled
|
||||
|
||||
```bash
|
||||
kubectl get pods -o wide
|
||||
|
||||
NAME READY STATUS RESTARTS AGE IP NODE
|
||||
nginx-6799fc88d8-4sgcb 1/1 Running 0 33s 172.12.121.1 worker02
|
||||
```
|
||||
|
||||
#### Port Forwarding
|
||||
Verify the ability to access applications remotely using port forwarding.
|
||||
|
||||
Retrieve the full name of the `nginx` pod:
|
||||
|
||||
```bash
|
||||
POD_NAME=$(kubectl get pods -l app=nginx -o jsonpath="{.items[0].metadata.name}")
|
||||
```
|
||||
|
||||
Forward port 8080 on your local machine to port 80 of the `nginx` pod:
|
||||
|
||||
```bash
|
||||
kubectl port-forward $POD_NAME 8080:80
|
||||
|
||||
Forwarding from 127.0.0.1:8080 -> 80
|
||||
Forwarding from [::1]:8080 -> 80
|
||||
```
|
||||
|
||||
In a new terminal make an HTTP request using the forwarding address:
|
||||
|
||||
```bash
|
||||
curl --head http://127.0.0.1:8080
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Server: nginx/1.21.0
|
||||
Date: Sat, 19 Jun 2021 08:19:01 GMT
|
||||
Content-Type: text/html
|
||||
Content-Length: 612
|
||||
Last-Modified: Tue, 25 May 2021 12:28:56 GMT
|
||||
Connection: keep-alive
|
||||
ETag: "60aced88-264"
|
||||
Accept-Ranges: bytes
|
||||
```
|
||||
|
||||
Switch back to the previous terminal and stop the port forwarding to the `nginx` pod.
|
||||
|
||||
#### Logs
|
||||
Verify the ability to retrieve container logs.
|
||||
|
||||
Print the `nginx` pod logs:
|
||||
|
||||
```bash
|
||||
kubectl logs $POD_NAME
|
||||
...
|
||||
127.0.0.1 - - [19/Jun/2021:08:19:01 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.68.0" "-"
|
||||
```
|
||||
|
||||
#### Kubelet tunnel
|
||||
Verify the ability to execute commands in a container.
|
||||
|
||||
Print the `nginx` version by executing the `nginx -v` command in the `nginx` container:
|
||||
|
||||
```bash
|
||||
kubectl exec -ti $POD_NAME -- nginx -v
|
||||
nginx version: nginx/1.21.0
|
||||
```
|
||||
|
||||
#### Services
|
||||
Verify the ability to expose applications using a service.
|
||||
|
||||
Expose the `nginx` deployment using a `NodePort` service:
|
||||
|
||||
```bash
|
||||
kubectl expose deployment nginx --port 80 --type NodePort
|
||||
```
|
||||
|
||||
Retrieve the node port assigned to the `nginx` service:
|
||||
|
||||
```bash
|
||||
NODE_PORT=$(kubectl get svc nginx \
|
||||
--output=jsonpath='{range .spec.ports[0]}{.nodePort}')
|
||||
```
|
||||
|
||||
Retrieve the IP address of a worker instance and make an HTTP request:
|
||||
|
||||
```bash
|
||||
curl -I http://${WORKER0}:${NODE_PORT}
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Server: nginx/1.21.0
|
||||
Date: Sat, 19 Jun 2021 09:29:01 GMT
|
||||
Content-Type: text/html
|
||||
Content-Length: 612
|
||||
Last-Modified: Tue, 25 May 2021 12:28:56 GMT
|
||||
Connection: keep-alive
|
||||
ETag: "60aced88-264"
|
||||
Accept-Ranges: bytes
|
||||
tenant-00-worker-00 Ready <none> 2m48s v1.25.0
|
||||
tenant-00-worker-01 Ready <none> 2m40s v1.25.0
|
||||
tenant-00-worker-02 Ready <none> 2m32s v1.25.0
|
||||
```
|
||||
|
||||
## Cleanup
|
||||
Remove the worker nodes joined the tenant control plane
|
||||
|
||||
```bash
|
||||
kubectl delete nodes --all --kubeconfig=${TENANT_NAMESPACE}-${TENANT_NAME}.kubeconfig
|
||||
kubectl --kubeconfig=${TENANT_NAMESPACE}-${TENANT_NAME}.kubeconfig delete nodes --all
|
||||
```
|
||||
|
||||
For each worker node, login and clean it
|
||||
@@ -475,3 +329,5 @@ Delete the tenant control plane from kamaji
|
||||
```bash
|
||||
kubectl delete -f ${TENANT_NAMESPACE}-${TENANT_NAME}-tcp.yaml
|
||||
```
|
||||
|
||||
That's all folks!
|
||||
5
docs/content/guides/mysql-datastore.md
Normal file
5
docs/content/guides/mysql-datastore.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# MySQL as Kubernetes Storage
|
||||
|
||||
Kamaji offers the possibility of having a different storage system than `ETCD` thanks to [kine](https://github.com/k3s-io/kine) integration. One of the implementations is [MySQL](https://www.mysql.com/).
|
||||
|
||||
> A detailed guide for production setup will be released soon. Please refer to [Getting Started Guide](../getting-started.md) for a demo setup with KinD.
|
||||
6
docs/content/guides/postgresql-datastore.md
Normal file
6
docs/content/guides/postgresql-datastore.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# PostgreSQL as Kubernetes Storage
|
||||
|
||||
Kamaji offers the possibility of having a different storage system than `etcd` thanks to [kine](https://github.com/k3s-io/kine) integration.
|
||||
One of the implementations is [PostgreSQL](https://www.postgresql.org/).
|
||||
|
||||
> A detailed guide for production setup will be released soon. Please refer to [Getting Started Guide](../getting-started.md) for a demo setup with KinD.
|
||||
13
docs/content/guides/upgrade.md
Normal file
13
docs/content/guides/upgrade.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Tenant Cluster Upgrade
|
||||
The process of upgrading a _“tenant cluster”_ consists in two steps:
|
||||
|
||||
1. Upgrade the Tenant Control Plane
|
||||
2. Upgrade of Tenant Worker Nodes
|
||||
|
||||
## Upgrade of Tenant Control Plane
|
||||
You should patch the `TenantControlPlane.spec.kubernetes.version` custom resource with a new compatible value according to the [Version Skew Policy](https://kubernetes.io/releases/version-skew-policy/).
|
||||
|
||||
> Note: during the upgrade, a new ReplicaSet of Tenant Control Plane pod will be created, so make sure you have at least two pods to avoid service disruption.
|
||||
|
||||
## Upgrade of Tenant Worker Nodes
|
||||
As currently Kamaji is not providing any helpers for Tenant Worker Nodes, you should make sure to upgrade them manually, for example, with the help of `kubeadm`. We have in roadmap, the Cluster APIs support so that you can upgrade _“tenant clusters”_ in a fully declarative way.
|
||||
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 184 KiB |
51
docs/content/index.md
Normal file
51
docs/content/index.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Kamaji
|
||||
**Kamaji** deploys and operates Kubernetes at scale with a fraction of the operational burden.
|
||||
|
||||
## How it works
|
||||
Kamaji turns any Kubernetes cluster into an _“admin cluster”_ to orchestrate other Kubernetes clusters called _“tenant clusters”_. What makes Kamaji special is that Control Planes of _“tenant clusters”_ are just regular pods running in the _“admin cluster”_ instead of dedicated Virtual Machines. This solution makes running control planes at scale cheaper and easier to deploy and operate. View [Concepts](concepts.md) for a deeper understanding of principles behind Kamaji's design.
|
||||
|
||||

|
||||

|
||||
|
||||
All the tenant clusters built with Kamaji are fully compliant [CNCF Certified Kubernetes](https://www.cncf.io/certification/software-conformance/) and are compatible with the standard toolchains everybody knows and loves.
|
||||
|
||||
<p align="center" style="padding: 6px 6px">
|
||||
<img src="https://raw.githubusercontent.com/cncf/artwork/master/projects/kubernetes/certified-kubernetes/versionless/color/certified-kubernetes-color.png" width="200" />
|
||||
</p>
|
||||
|
||||
## Features
|
||||
|
||||
- **Self Service Kubernetes:** leave users the freedom to self-provision their Kubernetes clusters according to the assigned boundaries.
|
||||
- **Multi-cluster Management:** centrally manage multiple tenant clusters from a single admin cluster. Happy SREs.
|
||||
- **Cheaper Control Planes:** place multiple tenant control planes on a single node, instead of having three nodes for a single control plane.
|
||||
- **Stronger Multi-Tenancy:** leave tenants to access the control plane with admin permissions while keeping the tenant isolated at the infrastructure level.
|
||||
- **Kubernetes Inception:** use Kubernetes to manage Kubernetes by re-using all the Kubernetes goodies you already know and love.
|
||||
- **Full APIs compliant:** tenant clusters are fully CNCF compliant built with upstream Kubernetes binaries. A user does not see differences between a Kamaji provisioned cluster and a dedicated cluster.
|
||||
|
||||
## Getting started
|
||||
|
||||
Please refer to the [Getting Started guide](getting-started.md) to deploy a minimal setup of Kamaji on [KinD](https://kind.sigs.k8s.io/).
|
||||
|
||||
## Open Source
|
||||
Kamaji is Open Source with Apache 2 license and any contribution is welcome. Open an issue or suggest an enhancement on the GitHub [project's page](https://github.com/clastix/kamaji). Join the [Kubernetes Slack Workspace](https://slack.k8s.io/) and the [`#kamaji`](https://kubernetes.slack.com/archives/C03GLTTMWNN) channel to meet end-users and contributors.
|
||||
|
||||
## FAQs
|
||||
Q. What does Kamaji mean?
|
||||
|
||||
A. Kamaji is named as the character _Kamaji_ from the Japanese movie [_Spirited Away_](https://en.wikipedia.org/wiki/Spirited_Away).
|
||||
|
||||
Q. Is Kamaji another Kubernetes distribution?
|
||||
|
||||
A. No, Kamaji is a Kubernetes Operator you can install on top of any Kubernetes cluster to provide hundreds or thousands of managed Kubernetes clusters as a service. We tested Kamaji on vanilla Kubernetes 1.22+, KinD, and Azure AKS. We expect it to work smoothly on other Kubernetes distributions. The tenant clusters made with Kamaji are conformant CNCF Kubernetes clusters as we leverage [`kubeadm`](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/).
|
||||
|
||||
Q. Is it safe to run Kubernetes control plane components in a pod instead of dedicated virtual machines?
|
||||
|
||||
A. Yes, the tenant control plane components are packaged in the same way they are running in bare metal or virtual nodes. We leverage the `kubeadm` code to set up the control plane components as they were running on their own server. The unchanged images of upstream `kube-apiserver`, `kube-scheduler`, and `kube-controller-manager` are used.
|
||||
|
||||
Q. You already provide a Kubernetes multi-tenancy solution with [Capsule](https://capsule.clastix.io). Why does Kamaji matter?
|
||||
|
||||
A. A multi-tenancy solution, like Capsule shares the Kubernetes control plane among all tenants keeping tenant namespaces isolated by policies. While the solution is the right choice by balancing between features and ease of usage, there are cases where a tenant user requires access to the control plane, for example, when a tenant requires to manage CRDs on his own. With Kamaji, you can provide cluster admin permissions to the tenant.
|
||||
|
||||
Q. Well you convinced me, how to get a try?
|
||||
|
||||
A. It is possible to get started with Kamaji on a laptop with [KinD](getting-started.md) installed.
|
||||
5530
docs/content/reference/api.md
Normal file
5530
docs/content/reference/api.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -39,76 +39,3 @@ Available environment variables are:
|
||||
| `KAMAJI_HEALTH_PROBE_BIND_ADDRESS` | The address the probe endpoint binds to. (default ":8081") |
|
||||
| `KAMAJI_LEADER_ELECTION` | Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager. |
|
||||
| `KAMAJI_TMP_DIRECTORY` | Directory which will be used to work with temporary files. (default "/tmp/kamaji") |
|
||||
|
||||
|
||||
## Build and deploy
|
||||
Clone the repo on your workstation.
|
||||
|
||||
```bash
|
||||
## Install dependencies
|
||||
$ go mod tidy
|
||||
|
||||
## Generate code
|
||||
$ make generate
|
||||
|
||||
## Generate Manifests
|
||||
$ make manifests
|
||||
|
||||
## Install Manifests
|
||||
$ make install
|
||||
|
||||
## Build Docker Image
|
||||
$ IMG=<image name and tag> make docker-build
|
||||
|
||||
## Push Docker Image
|
||||
$ IMG=<image name and tag> make docker-push
|
||||
|
||||
## Deploy Kamaji
|
||||
$ IMG=<image name and tag> make deploy
|
||||
|
||||
## YAML Installation File
|
||||
$ make yaml-installation-file
|
||||
|
||||
```
|
||||
|
||||
It will generate a yaml installation file at `config/install.yaml`. It should be customize accordingly.
|
||||
|
||||
## Tenant Control Planes
|
||||
|
||||
**Kamaji** offers a [CRD](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/) to provide a declarative approach of managing tenant control planes. This *CRD* is called `TenantControlPlane`, or `tcp` in short. Use the command `kubectl explain tcp.spec` to understand the fields and their usage.
|
||||
|
||||
### Add-ons
|
||||
|
||||
**Kamaji** provides optional installations into the deployed tenant control plane through add-ons. Is it possible to enable/disable them through the `tcp` definition.
|
||||
|
||||
### Core DNS
|
||||
|
||||
```yaml
|
||||
addons:
|
||||
coreDNS: {}
|
||||
```
|
||||
|
||||
### Kube-Proxy
|
||||
|
||||
```yaml
|
||||
addons:
|
||||
kubeProxy: {}
|
||||
```
|
||||
|
||||
### Konnectivity
|
||||
|
||||
```yaml
|
||||
addons:
|
||||
konnectivity:
|
||||
proxyPort: 31132 # mandatory
|
||||
version: v0.0.31
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
serverImage: us.gcr.io/k8s-artifacts-prod/kas-network-proxy/proxy-server
|
||||
agentImage: us.gcr.io/k8s-artifacts-prod/kas-network-proxy/proxy-agent
|
||||
```
|
||||
70
docs/content/reference/conformance.md
Normal file
70
docs/content/reference/conformance.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Conformance
|
||||
For organizations using Kubernetes, conformance enables interoperability, consistency, and confirmability between Kubernetes installations. The Cloud Computing Native Foundation - CNCF - provides the [Certified Kubernetes Conformance Program](https://www.cncf.io/certification/software-conformance/).
|
||||
|
||||
The standard set of conformance tests is currently those defined by the `[Conformance]` tag in the
|
||||
[kubernetes e2e](https://github.com/kubernetes/kubernetes/tree/master/test/e2e) suite.
|
||||
|
||||
All the _“tenant clusters”_ built with Kamaji are CNCF conformant:
|
||||
|
||||
- [v1.23](https://github.com/cncf/k8s-conformance/pull/2194)
|
||||
- [v1.24](https://github.com/cncf/k8s-conformance/pull/2193)
|
||||
- [v1.25](https://github.com/cncf/k8s-conformance/pull/2188)
|
||||
|
||||
<p align="left" style="padding: 6px 6px">
|
||||
<img src="https://raw.githubusercontent.com/cncf/artwork/master/projects/kubernetes/certified-kubernetes/versionless/color/certified-kubernetes-color.png" width="100" />
|
||||
</p>
|
||||
|
||||
## Running the conformance tests
|
||||
|
||||
The standard tool for running CNCF conformance tests is [Sonobuoy](https://github.com/vmware-tanzu/sonobuoy). Sonobuoy is
|
||||
regularly built and kept up to date to execute against all currently supported versions of kubernetes.
|
||||
|
||||
Download a [binary release](https://github.com/vmware-tanzu/sonobuoy/releases) of the CLI.
|
||||
|
||||
Make sure to access your tenant cluster:
|
||||
|
||||
```
|
||||
export KUBECONFIG=tenant.kubeconfig
|
||||
```
|
||||
|
||||
Deploy a Sonobuoy pod to your tenant cluster with:
|
||||
|
||||
```
|
||||
sonobuoy run --mode=certified-conformance
|
||||
```
|
||||
|
||||
> You can run the command synchronously by adding the flag `--wait` but be aware that running the conformance tests can take an hour or more.
|
||||
|
||||
View actively running pods:
|
||||
|
||||
```
|
||||
sonobuoy status
|
||||
```
|
||||
|
||||
To inspect the logs:
|
||||
|
||||
```
|
||||
sonobuoy logs -f
|
||||
```
|
||||
|
||||
Once `sonobuoy status` shows the run as `completed`, copy the output directory from the main Sonobuoy pod to a local directory:
|
||||
|
||||
```
|
||||
outfile=$(sonobuoy retrieve)
|
||||
```
|
||||
|
||||
This copies a single `.tar.gz` snapshot from the Sonobuoy pod into your local
|
||||
`.` directory. Extract the contents into `./results` with:
|
||||
|
||||
```
|
||||
mkdir ./results; tar xzf $outfile -C ./results
|
||||
```
|
||||
|
||||
To clean up Kubernetes objects created by Sonobuoy, run:
|
||||
|
||||
```
|
||||
sonobuoy delete
|
||||
```
|
||||
|
||||
|
||||
|
||||
2
docs/content/reference/index.md
Normal file
2
docs/content/reference/index.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# Reference
|
||||
This section of the Kamaji documentation contains references to the project's specifications.
|
||||
@@ -1,8 +1,10 @@
|
||||
# Versioning and support
|
||||
# Versioning
|
||||
|
||||
In Kamaji, there are different components that might require independent versioning and support level:
|
||||
|
||||
|Kamaji|Admin Cluster|Tenant Cluster (min)|Tenant Cluster (max)|Konnectivity|Tenant etcd |
|
||||
|------|-------------|--------------------|--------------------|------------|------------|
|
||||
|0.0.1 |1.22.0+ |1.21.0 |1.23.x |0.0.32 |3.5.4 |
|
||||
|0.0.1 |1.22.0+ |1.21.0 |1.23.5 |0.0.31 |3.5.4 |
|
||||
|0.0.2 |1.22.0+ |1.21.0 |1.25.0 |0.0.32 |3.5.4 |
|
||||
|
||||
Other combinations might work but have not been tested.
|
||||
Other combinations might work but they have not been yet tested.
|
||||
11
docs/content/use-cases.md
Normal file
11
docs/content/use-cases.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Use Cases
|
||||
Kamaji project has been initially started as a solution for actual and common problems such as minimizing the Total Cost of Ownership while running Kubernetes at large scale. However, it can open a wider range of use cases.
|
||||
|
||||
Here are a few:
|
||||
|
||||
- **Managed Kubernetes:** enable companies to provide Cloud Native Infrastructure with ease by introducing a strong separation of concerns between management and workloads. Centralize clusters management, monitoring, and observability by leaving developers to focus on applications, increase productivity and reduce operational costs.
|
||||
- **Kubernetes as a Service:** provide Kubernetes clusters in a self-service fashion by running management and workloads on different infrastructures with the option of Bring Your Own Device, BYOD.
|
||||
- **Control Plane as a Service:** provide multiple Kubernetes control planes running on top of a single Kubernetes cluster. Tenants who use namespaces based isolation often still need access to cluster wide resources like Cluster Roles, Admission Webhooks, or Custom Resource Definitions.
|
||||
- **Edge Computing:** distribute Kubernetes workloads across edge computing locations without having to manage multiple clusters across various providers. Centralize management of hundreds of control planes while leaving workloads to run isolated on their own dedicated infrastructure.
|
||||
- **Cluster Simulation:** check new Kubernetes API or experimental flag or a new tool without impacting production operations. Kamaji will let you simulate such things in a safe and controlled environment.
|
||||
- **Workloads Testing:** check the behaviour of your workloads on different and multiple versions of Kubernetes with ease by deploying multiple Control Planes in a single cluster.
|
||||
61
docs/mkdocs.yml
Normal file
61
docs/mkdocs.yml
Normal file
@@ -0,0 +1,61 @@
|
||||
site_name: Kamaji
|
||||
repo_name: clastix/kamaji
|
||||
repo_url: https://github.com/clastix/kamaji
|
||||
site_url: https://kamaji.clastix.io/
|
||||
docs_dir: content
|
||||
site_dir: site
|
||||
|
||||
theme:
|
||||
name: material
|
||||
features:
|
||||
- navigation.tabs
|
||||
- navigation.tabs.sticky
|
||||
- navigation.instant
|
||||
- navigation.sections
|
||||
include_sidebar: true
|
||||
palette:
|
||||
|
||||
# Palette toggle for automatic mode
|
||||
- media: "(prefers-color-scheme)"
|
||||
toggle:
|
||||
icon: material/brightness-auto
|
||||
name: Switch to light mode
|
||||
|
||||
# Palette toggle for light mode
|
||||
- media: "(prefers-color-scheme: light)"
|
||||
scheme: default
|
||||
toggle:
|
||||
icon: material/lightbulb
|
||||
name: Switch to dark mode
|
||||
|
||||
# Palette toggle for dark mode
|
||||
- media: "(prefers-color-scheme: dark)"
|
||||
scheme: slate
|
||||
toggle:
|
||||
icon: material/lightbulb-outline
|
||||
name: Switch to system preference
|
||||
|
||||
# Generate navigation bar
|
||||
nav:
|
||||
- 'Kamaji': index.md
|
||||
- 'Getting started': getting-started.md
|
||||
- 'Concepts': concepts.md
|
||||
- 'Guides':
|
||||
- guides/index.md
|
||||
- guides/kamaji-deployment-guide.md
|
||||
- guides/kamaji-azure-deployment-guide.md
|
||||
- guides/postgresql-datastore.md
|
||||
- guides/mysql-datastore.md
|
||||
- guides/upgrade.md
|
||||
- 'Use Cases': use-cases.md
|
||||
- 'Reference':
|
||||
- reference/index.md
|
||||
- reference/configuration.md
|
||||
- reference/conformance.md
|
||||
- reference/versioning.md
|
||||
- reference/api.md
|
||||
- 'Contribute':
|
||||
- contribute/index.md
|
||||
- contribute/guidelines.md
|
||||
- contribute/governance.md
|
||||
|
||||
2
docs/requirements.txt
Normal file
2
docs/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
mkdocs>=1.3.0
|
||||
mkdocs-material>=8.2.8
|
||||
1
docs/runtime.txt
Normal file
1
docs/runtime.txt
Normal file
@@ -0,0 +1 @@
|
||||
3.8
|
||||
94
docs/templates/reference-cr.tmpl
vendored
Normal file
94
docs/templates/reference-cr.tmpl
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
# API Reference
|
||||
|
||||
Packages:
|
||||
{{range .Groups}}
|
||||
- [{{.Group}}/{{.Version}}](#{{ anchorize (printf "%s/%s" .Group .Version) }})
|
||||
{{- end -}}{{/* range .Groups */}}
|
||||
|
||||
{{- range .Groups }}
|
||||
{{- $group := . }}
|
||||
|
||||
# {{.Group}}/{{.Version}}
|
||||
|
||||
Resource Types:
|
||||
{{range .Kinds}}
|
||||
- [{{.Name}}](#{{ anchorize .Name }})
|
||||
{{end}}{{/* range .Kinds */}}
|
||||
|
||||
{{range .Kinds}}
|
||||
{{$kind := .}}
|
||||
## {{.Name}}
|
||||
|
||||
{{range .Types}}
|
||||
|
||||
{{if not .IsTopLevel}}
|
||||
### {{.Name}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{.Description}}
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Required</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{- if .IsTopLevel -}}
|
||||
<tr>
|
||||
<td><b>apiVersion</b></td>
|
||||
<td>string</td>
|
||||
<td>{{$group.Group}}/{{$group.Version}}</td>
|
||||
<td>true</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>kind</b></td>
|
||||
<td>string</td>
|
||||
<td>{{$kind.Name}}</td>
|
||||
<td>true</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b><a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#objectmeta-v1-meta">metadata</a></b></td>
|
||||
<td>object</td>
|
||||
<td>Refer to the Kubernetes API documentation for the fields of the `metadata` field.</td>
|
||||
<td>true</td>
|
||||
</tr>
|
||||
{{- end -}}
|
||||
{{- range .Fields -}}
|
||||
<tr>
|
||||
<td><b>{{if .TypeKey}}<a href="#{{.TypeKey}}">{{.Name}}</a>{{else}}{{.Name}}{{end}}</b></td>
|
||||
<td>{{.Type}}</td>
|
||||
<td>
|
||||
{{.Description}}<br/>
|
||||
{{- if or .Schema.Format .Schema.Enum .Schema.Default .Schema.Minimum .Schema.Maximum }}
|
||||
<br/>
|
||||
{{- end}}
|
||||
{{- if .Schema.Format }}
|
||||
<i>Format</i>: {{ .Schema.Format }}<br/>
|
||||
{{- end }}
|
||||
{{- if .Schema.Enum }}
|
||||
<i>Enum</i>: {{ .Schema.Enum | toStrings | join ", " }}<br/>
|
||||
{{- end }}
|
||||
{{- if .Schema.Default }}
|
||||
<i>Default</i>: {{ .Schema.Default }}<br/>
|
||||
{{- end }}
|
||||
{{- if .Schema.Minimum }}
|
||||
<i>Minimum</i>: {{ .Schema.Minimum }}<br/>
|
||||
{{- end }}
|
||||
{{- if .Schema.Maximum }}
|
||||
<i>Maximum</i>: {{ .Schema.Maximum }}<br/>
|
||||
{{- end }}
|
||||
</td>
|
||||
<td>{{.Required}}</td>
|
||||
</tr>
|
||||
{{- end -}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{- end}}{{/* range .Types */}}
|
||||
{{- end}}{{/* range .Kinds */}}
|
||||
{{- end}}{{/* range .Groups */}}
|
||||
139
go.mod
139
go.mod
@@ -3,30 +3,27 @@ module github.com/clastix/kamaji
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/go-logr/logr v1.2.0
|
||||
github.com/go-logr/logr v1.2.3
|
||||
github.com/go-pg/pg/v10 v10.10.6
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/onsi/ginkgo v1.16.5
|
||||
github.com/onsi/gomega v1.17.0
|
||||
github.com/onsi/gomega v1.19.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.10.1
|
||||
github.com/testcontainers/testcontainers-go v0.13.0
|
||||
go.etcd.io/etcd/api/v3 v3.5.1
|
||||
go.etcd.io/etcd/client/v3 v3.5.1
|
||||
google.golang.org/grpc v1.43.0
|
||||
k8s.io/api v0.23.5
|
||||
k8s.io/apimachinery v0.23.5
|
||||
k8s.io/apiserver v0.23.5
|
||||
k8s.io/client-go v0.23.5
|
||||
go.etcd.io/etcd/api/v3 v3.5.4
|
||||
go.etcd.io/etcd/client/v3 v3.5.4
|
||||
k8s.io/api v0.25.0
|
||||
k8s.io/apimachinery v0.25.0
|
||||
k8s.io/apiserver v0.25.0
|
||||
k8s.io/client-go v0.25.0
|
||||
k8s.io/cluster-bootstrap v0.0.0
|
||||
k8s.io/component-base v0.23.5
|
||||
k8s.io/kube-proxy v0.0.0
|
||||
k8s.io/kubelet v0.0.0
|
||||
k8s.io/kubernetes v1.23.5
|
||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
|
||||
k8s.io/kubernetes v1.25.0
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed
|
||||
sigs.k8s.io/controller-runtime v0.11.0
|
||||
)
|
||||
|
||||
@@ -35,8 +32,8 @@ require (
|
||||
cloud.google.com/go/storage v1.18.2 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.18 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.27 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
@@ -45,7 +42,7 @@ require (
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.2 // indirect
|
||||
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
@@ -54,34 +51,35 @@ require (
|
||||
github.com/containerd/cgroups v1.0.1 // indirect
|
||||
github.com/containerd/containerd v1.5.9 // indirect
|
||||
github.com/coredns/caddy v1.1.0 // indirect
|
||||
github.com/coredns/corefile-migration v1.0.14 // indirect
|
||||
github.com/coredns/corefile-migration v1.0.17 // indirect
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/docker v20.10.11+incompatible // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/envoyproxy/go-control-plane v0.10.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-logr/zapr v1.2.0 // indirect
|
||||
github.com/go-logr/zapr v1.2.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.5 // indirect
|
||||
github.com/go-openapi/swag v0.19.14 // indirect
|
||||
github.com/go-pg/zerochecker v0.2.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/google/gofuzz v1.1.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
|
||||
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
@@ -96,16 +94,17 @@ require (
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||
github.com/moby/sys/mount v0.2.0 // indirect
|
||||
github.com/moby/sys/mountinfo v0.5.0 // indirect
|
||||
github.com/moby/sys/mountinfo v0.6.0 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
github.com/opencontainers/runc v1.0.2 // indirect
|
||||
github.com/opencontainers/runc v1.1.3 // indirect
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
@@ -118,76 +117,78 @@ require (
|
||||
github.com/spf13/cast v1.4.1 // indirect
|
||||
github.com/spf13/cobra v1.4.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/stretchr/testify v1.7.0 // indirect
|
||||
github.com/subosito/gotenv v1.2.0 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
github.com/vmihailenco/bufpool v0.1.11 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect
|
||||
github.com/vmihailenco/tagparser v0.1.2 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.1 // indirect
|
||||
github.com/xlab/treeprint v1.1.0 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.4 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.19.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
|
||||
google.golang.org/api v0.63.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect
|
||||
google.golang.org/grpc v1.47.0 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.66.2 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
k8s.io/apiextensions-apiserver v0.23.5 // indirect
|
||||
k8s.io/cli-runtime v0.23.5 // indirect
|
||||
k8s.io/klog/v2 v2.60.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
|
||||
k8s.io/system-validators v1.6.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.25.0 // indirect
|
||||
k8s.io/cli-runtime v0.25.0 // indirect
|
||||
k8s.io/component-base v0.25.0 // indirect
|
||||
k8s.io/klog/v2 v2.70.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
|
||||
k8s.io/kube-proxy v0.0.0 // indirect
|
||||
k8s.io/system-validators v1.7.0 // indirect
|
||||
mellium.im/sasl v0.3.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.10.1 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.13.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.12.1 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
k8s.io/api => k8s.io/api v0.23.5
|
||||
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.23.5
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.23.5
|
||||
k8s.io/apiserver => k8s.io/apiserver v0.23.5
|
||||
k8s.io/cli-runtime => k8s.io/cli-runtime v0.23.5
|
||||
k8s.io/client-go => k8s.io/client-go v0.23.5
|
||||
k8s.io/cloud-provider => k8s.io/cloud-provider v0.23.5
|
||||
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.23.5
|
||||
k8s.io/code-generator => k8s.io/code-generator v0.23.5
|
||||
k8s.io/component-base => k8s.io/component-base v0.23.5
|
||||
k8s.io/component-helpers => k8s.io/component-helpers v0.23.5
|
||||
k8s.io/controller-manager => k8s.io/controller-manager v0.23.5
|
||||
k8s.io/cri-api => k8s.io/cri-api v0.23.5
|
||||
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.23.5
|
||||
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.23.5
|
||||
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.23.5
|
||||
k8s.io/kube-proxy => k8s.io/kube-proxy v0.23.5
|
||||
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.23.5
|
||||
k8s.io/kubectl => k8s.io/kubectl v0.23.5
|
||||
k8s.io/kubelet => k8s.io/kubelet v0.23.5
|
||||
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.23.5
|
||||
k8s.io/metrics => k8s.io/metrics v0.23.5
|
||||
k8s.io/mount-utils => k8s.io/mount-utils v0.23.5
|
||||
k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.23.5
|
||||
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.23.5
|
||||
k8s.io/api => k8s.io/api v0.25.0
|
||||
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.25.0
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.25.0
|
||||
k8s.io/apiserver => k8s.io/apiserver v0.25.0
|
||||
k8s.io/cli-runtime => k8s.io/cli-runtime v0.25.0
|
||||
k8s.io/client-go => k8s.io/client-go v0.25.0
|
||||
k8s.io/cloud-provider => k8s.io/cloud-provider v0.25.0
|
||||
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.25.0
|
||||
k8s.io/code-generator => k8s.io/code-generator v0.25.0
|
||||
k8s.io/component-base => k8s.io/component-base v0.25.0
|
||||
k8s.io/component-helpers => k8s.io/component-helpers v0.25.0
|
||||
k8s.io/controller-manager => k8s.io/controller-manager v0.25.0
|
||||
k8s.io/cri-api => k8s.io/cri-api v0.25.0
|
||||
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.25.0
|
||||
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.25.0
|
||||
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.25.0
|
||||
k8s.io/kube-proxy => k8s.io/kube-proxy v0.25.0
|
||||
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.25.0
|
||||
k8s.io/kubectl => k8s.io/kubectl v0.25.0
|
||||
k8s.io/kubelet => k8s.io/kubelet v0.25.0
|
||||
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.25.0
|
||||
k8s.io/metrics => k8s.io/metrics v0.25.0
|
||||
k8s.io/mount-utils => k8s.io/mount-utils v0.25.0
|
||||
k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.25.0
|
||||
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.25.0
|
||||
)
|
||||
|
||||
12
indexers/indexer.go
Normal file
12
indexers/indexer.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package indexers
|
||||
|
||||
import "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
type Indexer interface {
|
||||
Object() client.Object
|
||||
Field() string
|
||||
ExtractValue() client.IndexerFunc
|
||||
}
|
||||
40
indexers/tcp_useddatastore.go
Normal file
40
indexers/tcp_useddatastore.go
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package indexers
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
controllerruntime "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
)
|
||||
|
||||
const (
|
||||
TenantControlPlaneUsedDataStoreKey = "status.storage.dataStoreName"
|
||||
)
|
||||
|
||||
type TenantControlPlaneStatusDataStore struct{}
|
||||
|
||||
func (t *TenantControlPlaneStatusDataStore) Object() client.Object {
|
||||
return &kamajiv1alpha1.TenantControlPlane{}
|
||||
}
|
||||
|
||||
func (t *TenantControlPlaneStatusDataStore) Field() string {
|
||||
return TenantControlPlaneUsedDataStoreKey
|
||||
}
|
||||
|
||||
func (t *TenantControlPlaneStatusDataStore) ExtractValue() client.IndexerFunc {
|
||||
return func(object client.Object) []string {
|
||||
//nolint:forcetypeassert
|
||||
tcp := object.(*kamajiv1alpha1.TenantControlPlane)
|
||||
|
||||
return []string{tcp.Status.Storage.DataStoreName}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TenantControlPlaneStatusDataStore) SetupWithManager(ctx context.Context, mgr controllerruntime.Manager) error {
|
||||
return mgr.GetFieldIndexer().IndexField(ctx, t.Object(), t.Field(), t.ExtractValue())
|
||||
}
|
||||
@@ -143,7 +143,7 @@ func (d *Deployment) buildPKIVolume(podSpec *corev1.PodSpec, tcp *kamajiv1alpha1
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Projected: &corev1.ProjectedVolumeSource{
|
||||
Sources: sources,
|
||||
DefaultMode: pointer.Int32Ptr(420),
|
||||
DefaultMode: pointer.Int32(420),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -159,7 +159,7 @@ func (d *Deployment) buildCAVolume(podSpec *corev1.PodSpec, tcp *kamajiv1alpha1.
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: tcp.Status.Certificates.CA.SecretName,
|
||||
DefaultMode: pointer.Int32Ptr(420),
|
||||
DefaultMode: pointer.Int32(420),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -175,7 +175,7 @@ func (d *Deployment) buildSSLCertsVolume(podSpec *corev1.PodSpec, tcp *kamajiv1a
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: tcp.Status.Certificates.CA.SecretName,
|
||||
DefaultMode: pointer.Int32Ptr(420),
|
||||
DefaultMode: pointer.Int32(420),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -191,7 +191,7 @@ func (d *Deployment) buildShareCAVolume(podSpec *corev1.PodSpec, tcp *kamajiv1al
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: tcp.Status.Certificates.CA.SecretName,
|
||||
DefaultMode: pointer.Int32Ptr(420),
|
||||
DefaultMode: pointer.Int32(420),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -207,7 +207,7 @@ func (d *Deployment) buildLocalShareCAVolume(podSpec *corev1.PodSpec, tcp *kamaj
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: tcp.Status.Certificates.CA.SecretName,
|
||||
DefaultMode: pointer.Int32Ptr(420),
|
||||
DefaultMode: pointer.Int32(420),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -223,7 +223,7 @@ func (d *Deployment) buildSchedulerVolume(podSpec *corev1.PodSpec, tcp *kamajiv1
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: tcp.Status.KubeConfig.Scheduler.SecretName,
|
||||
DefaultMode: pointer.Int32Ptr(420),
|
||||
DefaultMode: pointer.Int32(420),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -239,7 +239,7 @@ func (d *Deployment) buildControllerManagerVolume(podSpec *corev1.PodSpec, tcp *
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: tcp.Status.KubeConfig.ControllerManager.SecretName,
|
||||
DefaultMode: pointer.Int32Ptr(420),
|
||||
DefaultMode: pointer.Int32(420),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -542,12 +542,13 @@ func (d *Deployment) buildKubeAPIServerCommand(tenantControlPlane *kamajiv1alpha
|
||||
"--kubelet-preferred-address-types": "Hostname,InternalIP,ExternalIP",
|
||||
"--proxy-client-cert-file": path.Join(v1beta3.DefaultCertificatesDir, constants.FrontProxyClientCertName),
|
||||
"--proxy-client-key-file": path.Join(v1beta3.DefaultCertificatesDir, constants.FrontProxyClientKeyName),
|
||||
"--requestheader-allowed-names": "front-proxy-client",
|
||||
"--requestheader-allowed-names": constants.FrontProxyClientCertCommonName,
|
||||
"--requestheader-client-ca-file": path.Join(v1beta3.DefaultCertificatesDir, constants.FrontProxyCACertName),
|
||||
"--requestheader-extra-headers-prefix": "X-Remote-Extra-",
|
||||
"--requestheader-group-headers": "X-Remote-Group",
|
||||
"--requestheader-username-headers": "X-Remote-User",
|
||||
"--secure-port": fmt.Sprintf("%d", tenantControlPlane.Spec.NetworkProfile.Port),
|
||||
"--service-account-issuer": fmt.Sprintf("https://localhost:%d", tenantControlPlane.Spec.NetworkProfile.Port),
|
||||
"--service-account-issuer": "https://kubernetes.default.svc.cluster.local",
|
||||
"--service-account-key-file": path.Join(v1beta3.DefaultCertificatesDir, constants.ServiceAccountPublicKeyName),
|
||||
"--service-account-signing-key-file": path.Join(v1beta3.DefaultCertificatesDir, constants.ServiceAccountPrivateKeyName),
|
||||
"--tls-cert-file": path.Join(v1beta3.DefaultCertificatesDir, constants.APIServerCertName),
|
||||
@@ -564,6 +565,7 @@ func (d *Deployment) buildKubeAPIServerCommand(tenantControlPlane *kamajiv1alpha
|
||||
httpsEndpoints = append(httpsEndpoints, fmt.Sprintf("https://%s", ep))
|
||||
}
|
||||
|
||||
desiredArgs["--etcd-compaction-interval"] = "0"
|
||||
desiredArgs["--etcd-prefix"] = fmt.Sprintf("/%s", tenantControlPlane.GetName())
|
||||
desiredArgs["--etcd-servers"] = strings.Join(httpsEndpoints, ",")
|
||||
desiredArgs["--etcd-cafile"] = "/etc/kubernetes/pki/etcd/ca.crt"
|
||||
@@ -617,7 +619,7 @@ func (d *Deployment) buildKineVolume(podSpec *corev1.PodSpec, tcp *kamajiv1alpha
|
||||
podSpec.Volumes[index].VolumeSource = corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: tcp.Status.Storage.Certificate.SecretName,
|
||||
DefaultMode: pointer.Int32Ptr(420),
|
||||
DefaultMode: pointer.Int32(420),
|
||||
},
|
||||
}
|
||||
if d.DataStore.Spec.Driver == kamajiv1alpha1.EtcdDriver {
|
||||
@@ -772,6 +774,18 @@ func (d *Deployment) SetAnnotations(resource *appsv1.Deployment, annotations map
|
||||
resource.SetAnnotations(annotations)
|
||||
}
|
||||
|
||||
func (d *Deployment) SetTopologySpreadConstraints(spec *appsv1.DeploymentSpec, topologies []corev1.TopologySpreadConstraint) {
|
||||
defaultSelector := spec.Selector
|
||||
|
||||
for index, topology := range topologies {
|
||||
if topology.LabelSelector == nil {
|
||||
topologies[index].LabelSelector = defaultSelector
|
||||
}
|
||||
}
|
||||
|
||||
spec.Template.Spec.TopologySpreadConstraints = topologies
|
||||
}
|
||||
|
||||
// ResetKubeAPIServerFlags ensures that upon a change of the kube-apiserver extra flags the desired ones are properly
|
||||
// applied, also considering that the container could be lately patched by the konnectivity addon resources.
|
||||
func (d *Deployment) ResetKubeAPIServerFlags(resource *appsv1.Deployment, tcp *kamajiv1alpha1.TenantControlPlane) {
|
||||
@@ -804,3 +818,15 @@ func (d *Deployment) ResetKubeAPIServerFlags(resource *appsv1.Deployment, tcp *k
|
||||
|
||||
resource.GetAnnotations()[apiServerFlagsAnnotation] = fmt.Sprintf("%d", len(tcp.Spec.ControlPlane.Deployment.ExtraArgs.APIServer))
|
||||
}
|
||||
|
||||
func (d *Deployment) SetNodeSelector(spec *corev1.PodSpec, tcp *kamajiv1alpha1.TenantControlPlane) {
|
||||
spec.NodeSelector = tcp.Spec.ControlPlane.Deployment.NodeSelector
|
||||
}
|
||||
|
||||
func (d *Deployment) SetToleration(spec *corev1.PodSpec, tcp *kamajiv1alpha1.TenantControlPlane) {
|
||||
spec.Tolerations = tcp.Spec.ControlPlane.Deployment.Tolerations
|
||||
}
|
||||
|
||||
func (d *Deployment) SetAffinity(spec *corev1.PodSpec, tcp *kamajiv1alpha1.TenantControlPlane) {
|
||||
spec.Affinity = tcp.Spec.ControlPlane.Deployment.Affinity
|
||||
}
|
||||
|
||||
10
internal/constants/annotations.go
Normal file
10
internal/constants/annotations.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package constants
|
||||
|
||||
const (
|
||||
// Checksum is the annotation label that we use to store the checksum for the resource:
|
||||
// it allows to check by comparing it if the resource has been changed and must be aligned with the reconciliation.
|
||||
Checksum = "kamaji.clastix.io/checksum"
|
||||
)
|
||||
@@ -5,103 +5,31 @@ package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
cryptorand "crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
mathrand "math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
certBitSize = 2048
|
||||
)
|
||||
|
||||
func GetCertificateAndKeyPair(template *x509.Certificate, caCert []byte, caPrivKey []byte) (*bytes.Buffer, *bytes.Buffer, error) {
|
||||
caCertBytes, err := GetCertificate(caCert)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
// CheckPublicAndPrivateKeyValidity checks if the given bytes for the private and public keys are valid.
|
||||
func CheckPublicAndPrivateKeyValidity(publicKey []byte, privateKey []byte) (bool, error) {
|
||||
if len(publicKey) == 0 || len(privateKey) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
caPrivKeyBytes, err := GetPrivateKey(caPrivKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return GenerateCertificateKeyPairBytes(template, certBitSize, caCertBytes, caPrivKeyBytes)
|
||||
}
|
||||
|
||||
func GetCertificate(cert []byte) (*x509.Certificate, error) {
|
||||
pemContent, _ := pem.Decode(cert)
|
||||
if pemContent == nil {
|
||||
return nil, fmt.Errorf("no right PEM block")
|
||||
}
|
||||
|
||||
return x509.ParseCertificate(pemContent.Bytes)
|
||||
}
|
||||
|
||||
func GetPrivateKey(privKey []byte) (*rsa.PrivateKey, error) {
|
||||
pemContent, _ := pem.Decode(privKey)
|
||||
if pemContent == nil {
|
||||
return nil, fmt.Errorf("no right PEM block")
|
||||
}
|
||||
|
||||
return x509.ParsePKCS1PrivateKey(pemContent.Bytes)
|
||||
}
|
||||
|
||||
func GetPublickKey(pubKey []byte) (*rsa.PublicKey, error) {
|
||||
pemContent, _ := pem.Decode(pubKey)
|
||||
if pemContent == nil {
|
||||
return nil, fmt.Errorf("no right PEM block")
|
||||
}
|
||||
|
||||
pub, err := x509.ParsePKIXPublicKey(pemContent.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pub.(*rsa.PublicKey), nil //nolint:forcetypeassert
|
||||
}
|
||||
|
||||
func GenerateCertificateKeyPairBytes(template *x509.Certificate, bitSize int, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*bytes.Buffer, *bytes.Buffer, error) {
|
||||
certPrivKey, err := rsa.GenerateKey(rand.Reader, bitSize)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
certBytes, err := x509.CreateCertificate(rand.Reader, template, caCert, &certPrivKey.PublicKey, caKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
certPEM := &bytes.Buffer{}
|
||||
if err := pem.Encode(certPEM, &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Headers: nil,
|
||||
Bytes: certBytes,
|
||||
}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
certPrivKeyPEM := &bytes.Buffer{}
|
||||
if err := pem.Encode(certPrivKeyPEM, &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Headers: nil,
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey),
|
||||
}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return certPEM, certPrivKeyPEM, nil
|
||||
}
|
||||
|
||||
func IsValidKeyPairBytes(pubKeyBytes []byte, privKeyBytes []byte) (bool, error) {
|
||||
privKey, err := GetPrivateKey(privKeyBytes)
|
||||
pubKey, err := ParsePublicKeyBytes(publicKey)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
pubKey, err := GetPublickKey(pubKeyBytes)
|
||||
privKey, err := ParsePrivateKeyBytes(privateKey)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -109,22 +37,134 @@ func IsValidKeyPairBytes(pubKeyBytes []byte, privKeyBytes []byte) (bool, error)
|
||||
return checkPublicKeys(privKey.PublicKey, *pubKey), nil
|
||||
}
|
||||
|
||||
func IsValidCertificateKeyPairBytes(certBytes []byte, privKeyBytes []byte) (bool, error) {
|
||||
cert, err := GetCertificate(certBytes)
|
||||
if err != nil {
|
||||
return false, err
|
||||
// CheckCertificateAndPrivateKeyPairValidity checks if the certificate and private key pair are valid.
|
||||
func CheckCertificateAndPrivateKeyPairValidity(certificate []byte, privateKey []byte) (bool, error) {
|
||||
switch {
|
||||
case len(certificate) == 0, len(privateKey) == 0:
|
||||
return false, nil
|
||||
default:
|
||||
return IsValidCertificateKeyPairBytes(certificate, privateKey)
|
||||
}
|
||||
|
||||
privKey, err := GetPrivateKey(privKeyBytes)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return isValidCertificateKeyPairBytes(*cert, *privKey), nil
|
||||
}
|
||||
|
||||
func isValidCertificateKeyPairBytes(cert x509.Certificate, privKey rsa.PrivateKey) bool {
|
||||
return checkCertificateValidity(cert) && checkCertificateKeyPair(cert, privKey)
|
||||
// GenerateCertificatePrivateKeyPair starts from the Certificate Authority bytes a certificate using the provided
|
||||
// template, returning the bytes both for the certificate and its key.
|
||||
func GenerateCertificatePrivateKeyPair(template *x509.Certificate, caCertificate []byte, caPrivateKey []byte) (*bytes.Buffer, *bytes.Buffer, error) {
|
||||
caCertBytes, err := ParseCertificateBytes(caCertificate)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
caPrivKeyBytes, err := ParsePrivateKeyBytes(caPrivateKey)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "provided CA private key for certificate generation cannot be parsed")
|
||||
}
|
||||
|
||||
return generateCertificateKeyPairBytes(template, caCertBytes, caPrivKeyBytes)
|
||||
}
|
||||
|
||||
// ParseCertificateBytes takes the certificate bytes returning a x509 certificate by parsing it.
|
||||
func ParseCertificateBytes(content []byte) (*x509.Certificate, error) {
|
||||
pemContent, _ := pem.Decode(content)
|
||||
if pemContent == nil {
|
||||
return nil, fmt.Errorf("no right PEM block")
|
||||
}
|
||||
|
||||
crt, err := x509.ParseCertificate(pemContent.Bytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot parse x509 Certificate")
|
||||
}
|
||||
|
||||
return crt, nil
|
||||
}
|
||||
|
||||
// ParsePrivateKeyBytes takes the private key bytes returning an RSA private key by parsing it.
|
||||
func ParsePrivateKeyBytes(content []byte) (*rsa.PrivateKey, error) {
|
||||
pemContent, _ := pem.Decode(content)
|
||||
if pemContent == nil {
|
||||
return nil, fmt.Errorf("no right PEM block")
|
||||
}
|
||||
|
||||
privateKey, err := x509.ParsePKCS1PrivateKey(pemContent.Bytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot parse PKCS1 Private Key")
|
||||
}
|
||||
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
// ParsePublicKeyBytes takes the public key bytes returning an RSA public key by parsing it.
|
||||
func ParsePublicKeyBytes(content []byte) (*rsa.PublicKey, error) {
|
||||
pemContent, _ := pem.Decode(content)
|
||||
if pemContent == nil {
|
||||
return nil, fmt.Errorf("no right PEM block")
|
||||
}
|
||||
|
||||
publicKey, err := x509.ParsePKIXPublicKey(pemContent.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rsaPublicKey, ok := publicKey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected *rsa.PublicKey, got %T", rsaPublicKey)
|
||||
}
|
||||
|
||||
return rsaPublicKey, nil
|
||||
}
|
||||
|
||||
// IsValidCertificateKeyPairBytes checks if the certificate matches the private key bounded to it.
|
||||
func IsValidCertificateKeyPairBytes(certificateBytes []byte, privateKeyBytes []byte) (bool, error) {
|
||||
crt, err := ParseCertificateBytes(certificateBytes)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
key, err := ParsePrivateKeyBytes(privateKeyBytes)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
switch {
|
||||
case !checkCertificateValidity(*crt):
|
||||
return false, nil
|
||||
case !checkPublicKeys(*crt.PublicKey.(*rsa.PublicKey), key.PublicKey): //nolint:forcetypeassert
|
||||
return false, nil
|
||||
default:
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
func generateCertificateKeyPairBytes(template *x509.Certificate, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*bytes.Buffer, *bytes.Buffer, error) {
|
||||
certPrivKey, err := rsa.GenerateKey(cryptorand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot generate an RSA key")
|
||||
}
|
||||
|
||||
certBytes, err := x509.CreateCertificate(cryptorand.Reader, template, caCert, &certPrivKey.PublicKey, caKey)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot create the certificate")
|
||||
}
|
||||
|
||||
certPEM := &bytes.Buffer{}
|
||||
if err = pem.Encode(certPEM, &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Headers: nil,
|
||||
Bytes: certBytes,
|
||||
}); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot encode the generate certificate bytes")
|
||||
}
|
||||
|
||||
certPrivKeyPEM := &bytes.Buffer{}
|
||||
if err = pem.Encode(certPrivKeyPEM, &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Headers: nil,
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey),
|
||||
}); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot encode private key")
|
||||
}
|
||||
|
||||
return certPEM, certPrivKeyPEM, nil
|
||||
}
|
||||
|
||||
func checkCertificateValidity(cert x509.Certificate) bool {
|
||||
@@ -133,13 +173,31 @@ func checkCertificateValidity(cert x509.Certificate) bool {
|
||||
return now.Before(cert.NotAfter) && now.After(cert.NotBefore)
|
||||
}
|
||||
|
||||
func checkCertificateKeyPair(cert x509.Certificate, privKey rsa.PrivateKey) bool {
|
||||
return checkPublicKeys(*cert.PublicKey.(*rsa.PublicKey), privKey.PublicKey) //nolint:forcetypeassert
|
||||
}
|
||||
|
||||
func checkPublicKeys(a rsa.PublicKey, b rsa.PublicKey) bool {
|
||||
isN := a.N.Cmp(b.N) == 0
|
||||
isE := a.E == b.E
|
||||
|
||||
return isN && isE
|
||||
}
|
||||
|
||||
// NewCertificateTemplate returns the template that must be used to generate a certificate,
|
||||
// used to perform the authentication against the DataStore.
|
||||
func NewCertificateTemplate(commonName string) *x509.Certificate {
|
||||
return &x509.Certificate{
|
||||
PublicKeyAlgorithm: x509.RSA,
|
||||
SerialNumber: big.NewInt(mathrand.Int63()),
|
||||
Subject: pkix.Name{
|
||||
CommonName: commonName,
|
||||
Organization: []string{"system:masters"},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(10, 0, 0),
|
||||
SubjectKeyId: []byte{1, 2, 3, 4, 6},
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{
|
||||
x509.ExtKeyUsageClientAuth,
|
||||
x509.ExtKeyUsageServerAuth,
|
||||
x509.ExtKeyUsageCodeSigning,
|
||||
},
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
}
|
||||
}
|
||||
|
||||
50
internal/datastore/errors/errors.go
Normal file
50
internal/datastore/errors/errors.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package errors
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
|
||||
func NewCreateUserError(err error) error {
|
||||
return errors.Wrap(err, "cannot create user")
|
||||
}
|
||||
|
||||
func NewGrantPrivilegesError(err error) error {
|
||||
return errors.Wrap(err, "cannot grant privileges")
|
||||
}
|
||||
|
||||
func NewCheckUserExistsError(err error) error {
|
||||
return errors.Wrap(err, "cannot check if user exists")
|
||||
}
|
||||
|
||||
func NewCheckGrantExistsError(err error) error {
|
||||
return errors.Wrap(err, "cannot check if grant exists")
|
||||
}
|
||||
|
||||
func NewDeleteUserError(err error) error {
|
||||
return errors.Wrap(err, "cannot delete user")
|
||||
}
|
||||
|
||||
func NewCannotDeleteDatabaseError(err error) error {
|
||||
return errors.Wrap(err, "cannot delete database")
|
||||
}
|
||||
|
||||
func NewCheckDatabaseExistError(err error) error {
|
||||
return errors.Wrap(err, "cannot check if database exists")
|
||||
}
|
||||
|
||||
func NewRevokePrivilegesError(err error) error {
|
||||
return errors.Wrap(err, "cannot revoke privileges")
|
||||
}
|
||||
|
||||
func NewCloseConnectionError(err error) error {
|
||||
return errors.Wrap(err, "cannot close connection")
|
||||
}
|
||||
|
||||
func NewCheckConnectionError(err error) error {
|
||||
return errors.Wrap(err, "cannot check connection")
|
||||
}
|
||||
|
||||
func NewCreateDBError(err error) error {
|
||||
return errors.Wrap(err, "cannot create database")
|
||||
}
|
||||
@@ -7,12 +7,13 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
goerrors "github.com/pkg/errors"
|
||||
"go.etcd.io/etcd/api/v3/authpb"
|
||||
"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
|
||||
etcdclient "go.etcd.io/etcd/client/v3"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/datastore/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -49,66 +50,64 @@ type EtcdClient struct {
|
||||
}
|
||||
|
||||
func (e *EtcdClient) CreateUser(ctx context.Context, user, password string) error {
|
||||
_, err := e.Client.Auth.UserAddWithOptions(ctx, user, password, &etcdclient.UserAddOptions{
|
||||
NoPassword: true,
|
||||
})
|
||||
if _, err := e.Client.Auth.UserAddWithOptions(ctx, user, password, &etcdclient.UserAddOptions{NoPassword: true}); err != nil {
|
||||
return errors.NewCreateUserError(err)
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EtcdClient) CreateDB(ctx context.Context, dbName string) error {
|
||||
func (e *EtcdClient) CreateDB(context.Context, string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EtcdClient) GrantPrivileges(ctx context.Context, user, dbName string) error {
|
||||
_, err := e.Client.Auth.RoleAdd(ctx, dbName)
|
||||
if err != nil {
|
||||
return err
|
||||
if _, err := e.Client.Auth.RoleAdd(ctx, dbName); err != nil {
|
||||
return errors.NewGrantPrivilegesError(err)
|
||||
}
|
||||
|
||||
permission := etcdclient.PermissionType(authpb.READWRITE)
|
||||
key := e.buildKey(dbName)
|
||||
if _, err = e.Client.RoleGrantPermission(ctx, user, key, rangeEnd, permission); err != nil {
|
||||
return err
|
||||
if _, err := e.Client.RoleGrantPermission(ctx, user, key, rangeEnd, permission); err != nil {
|
||||
return errors.NewGrantPrivilegesError(err)
|
||||
}
|
||||
|
||||
if _, err = e.Client.UserGrantRole(ctx, user, dbName); err != nil {
|
||||
return err
|
||||
if _, err := e.Client.UserGrantRole(ctx, user, dbName); err != nil {
|
||||
return errors.NewGrantPrivilegesError(err)
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EtcdClient) UserExists(ctx context.Context, user string) (bool, error) {
|
||||
_, err := e.Client.UserGet(ctx, user)
|
||||
if err != nil {
|
||||
if errors.As(err, &rpctypes.ErrGRPCUserNotFound) {
|
||||
if _, err := e.Client.UserGet(ctx, user); err != nil {
|
||||
if goerrors.As(err, &rpctypes.ErrGRPCUserNotFound) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
return false, errors.NewCheckUserExistsError(err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (e *EtcdClient) DBExists(_ context.Context, dbName string) (bool, error) {
|
||||
func (e *EtcdClient) DBExists(context.Context, string) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (e *EtcdClient) GrantPrivilegesExists(ctx context.Context, username, dbName string) (bool, error) {
|
||||
_, err := e.Client.RoleGet(ctx, dbName)
|
||||
if err != nil {
|
||||
if errors.As(err, &rpctypes.ErrGRPCRoleNotFound) {
|
||||
if goerrors.As(err, &rpctypes.ErrGRPCRoleNotFound) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
return false, errors.NewCheckGrantExistsError(err)
|
||||
}
|
||||
|
||||
user, err := e.Client.UserGet(ctx, username)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, errors.NewCheckGrantExistsError(err)
|
||||
}
|
||||
|
||||
for _, i := range user.Roles {
|
||||
@@ -121,23 +120,29 @@ func (e *EtcdClient) GrantPrivilegesExists(ctx context.Context, username, dbName
|
||||
}
|
||||
|
||||
func (e *EtcdClient) DeleteUser(ctx context.Context, user string) error {
|
||||
_, err := e.Client.Auth.UserDelete(ctx, user)
|
||||
if _, err := e.Client.Auth.UserDelete(ctx, user); err != nil {
|
||||
return errors.NewDeleteUserError(err)
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EtcdClient) DeleteDB(ctx context.Context, dbName string) error {
|
||||
withRange := etcdclient.WithRange(rangeEnd)
|
||||
prefix := e.buildKey(dbName)
|
||||
_, err := e.Client.Delete(ctx, prefix, withRange)
|
||||
if _, err := e.Client.Delete(ctx, prefix, withRange); err != nil {
|
||||
return errors.NewCannotDeleteDatabaseError(err)
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EtcdClient) RevokePrivileges(ctx context.Context, user, dbName string) error {
|
||||
_, err := e.Client.Auth.RoleDelete(ctx, dbName)
|
||||
if _, err := e.Client.Auth.RoleDelete(ctx, dbName); err != nil {
|
||||
return errors.NewRevokePrivilegesError(err)
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EtcdClient) GetConnectionString() string {
|
||||
@@ -147,13 +152,19 @@ func (e *EtcdClient) GetConnectionString() string {
|
||||
}
|
||||
|
||||
func (e *EtcdClient) Close() error {
|
||||
return e.Client.Close()
|
||||
if err := e.Client.Close(); err != nil {
|
||||
return errors.NewCloseConnectionError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EtcdClient) Check(ctx context.Context) error {
|
||||
_, err := e.Client.AuthStatus(ctx)
|
||||
if _, err := e.Client.AuthStatus(ctx); err != nil {
|
||||
return errors.NewCheckConnectionError(err)
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EtcdClient) Driver() string {
|
||||
@@ -163,9 +174,3 @@ func (e *EtcdClient) Driver() string {
|
||||
func (e *EtcdClient) buildKey(roleName string) string {
|
||||
return fmt.Sprintf("/%s/", roleName)
|
||||
}
|
||||
|
||||
type Permission struct {
|
||||
Type int `json:"type,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
RangeEnd string `json:"rangeEnd,omitempty"`
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-pg/pg/v10"
|
||||
"github.com/go-sql-driver/mysql"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/datastore/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -41,18 +41,6 @@ func (c *MySQLConnection) Driver() string {
|
||||
return string(kamajiv1alpha1.KineMySQLDriver)
|
||||
}
|
||||
|
||||
func NewPostgreSQLConnection(config ConnectionConfig) (Connection, error) {
|
||||
opt := &pg.Options{
|
||||
Addr: config.Endpoints[0].String(),
|
||||
Database: config.DBName,
|
||||
User: config.User,
|
||||
Password: config.Password,
|
||||
TLSConfig: config.TLSConfig,
|
||||
}
|
||||
|
||||
return &PostgreSQLConnection{db: pg.Connect(opt), connection: config.Endpoints[0]}, nil
|
||||
}
|
||||
|
||||
func NewMySQLConnection(config ConnectionConfig) (Connection, error) {
|
||||
nameDB := fmt.Sprintf("%s(%s)", defaultProtocol, config.Endpoints[0].String())
|
||||
|
||||
@@ -91,23 +79,43 @@ func (c *MySQLConnection) GetConnectionString() string {
|
||||
}
|
||||
|
||||
func (c *MySQLConnection) Close() error {
|
||||
return c.db.Close()
|
||||
if err := c.db.Close(); err != nil {
|
||||
return errors.NewCloseConnectionError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MySQLConnection) Check(ctx context.Context) error {
|
||||
return c.db.PingContext(ctx)
|
||||
if err := c.db.PingContext(ctx); err != nil {
|
||||
return errors.NewCheckConnectionError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MySQLConnection) CreateUser(ctx context.Context, user, password string) error {
|
||||
return c.mutate(ctx, mysqlCreateUserStatement, user, password)
|
||||
if err := c.mutate(ctx, mysqlCreateUserStatement, user, password); err != nil {
|
||||
return errors.NewCreateUserError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MySQLConnection) CreateDB(ctx context.Context, dbName string) error {
|
||||
return c.mutate(ctx, mysqlCreateDBStatement, dbName)
|
||||
if err := c.mutate(ctx, mysqlCreateDBStatement, dbName); err != nil {
|
||||
return errors.NewCreateDBError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MySQLConnection) GrantPrivileges(ctx context.Context, user, dbName string) error {
|
||||
return c.mutate(ctx, mysqlGrantPrivilegesStatement, user, dbName)
|
||||
if err := c.mutate(ctx, mysqlGrantPrivilegesStatement, user, dbName); err != nil {
|
||||
return errors.NewGrantPrivilegesError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MySQLConnection) UserExists(ctx context.Context, user string) (bool, error) {
|
||||
@@ -124,7 +132,12 @@ func (c *MySQLConnection) UserExists(ctx context.Context, user string) (bool, er
|
||||
return name == user, nil
|
||||
}
|
||||
|
||||
return c.check(ctx, mysqlFetchUserStatement, checker, user)
|
||||
ok, err := c.check(ctx, mysqlFetchUserStatement, checker, user)
|
||||
if err != nil {
|
||||
return false, errors.NewCheckUserExistsError(err)
|
||||
}
|
||||
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
func (c *MySQLConnection) DBExists(ctx context.Context, dbName string) (bool, error) {
|
||||
@@ -141,14 +154,19 @@ func (c *MySQLConnection) DBExists(ctx context.Context, dbName string) (bool, er
|
||||
return name == dbName, nil
|
||||
}
|
||||
|
||||
return c.check(ctx, mysqlFetchDBStatement, checker, dbName)
|
||||
ok, err := c.check(ctx, mysqlFetchDBStatement, checker, dbName)
|
||||
if err != nil {
|
||||
return false, errors.NewCheckDatabaseExistError(err)
|
||||
}
|
||||
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
func (c *MySQLConnection) GrantPrivilegesExists(ctx context.Context, user, dbName string) (bool, error) {
|
||||
statementShowGrantsStatement := fmt.Sprintf(mysqlShowGrantsStatement, user)
|
||||
rows, err := c.db.Query(statementShowGrantsStatement)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, errors.NewGrantPrivilegesError(err)
|
||||
}
|
||||
|
||||
expected := fmt.Sprintf(mysqlGrantPrivilegesStatement, user, dbName)
|
||||
@@ -156,7 +174,7 @@ func (c *MySQLConnection) GrantPrivilegesExists(ctx context.Context, user, dbNam
|
||||
|
||||
for rows.Next() {
|
||||
if err = rows.Scan(&grant); err != nil {
|
||||
return false, err
|
||||
return false, errors.NewGrantPrivilegesError(err)
|
||||
}
|
||||
|
||||
if grant == expected {
|
||||
@@ -168,15 +186,27 @@ func (c *MySQLConnection) GrantPrivilegesExists(ctx context.Context, user, dbNam
|
||||
}
|
||||
|
||||
func (c *MySQLConnection) DeleteUser(ctx context.Context, user string) error {
|
||||
return c.mutate(ctx, mysqlDropUserStatement, user)
|
||||
if err := c.mutate(ctx, mysqlDropUserStatement, user); err != nil {
|
||||
return errors.NewDeleteUserError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MySQLConnection) DeleteDB(ctx context.Context, dbName string) error {
|
||||
return c.mutate(ctx, mysqlDropDBStatement, dbName)
|
||||
if err := c.mutate(ctx, mysqlDropDBStatement, dbName); err != nil {
|
||||
return errors.NewCannotDeleteDatabaseError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MySQLConnection) RevokePrivileges(ctx context.Context, user, dbName string) error {
|
||||
return c.mutate(ctx, mysqlRevokePrivilegesStatement, user, dbName)
|
||||
if err := c.mutate(ctx, mysqlRevokePrivilegesStatement, user, dbName); err != nil {
|
||||
return errors.NewRevokePrivilegesError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MySQLConnection) check(ctx context.Context, nonFilledStatement string, checker func(*sql.Row) (bool, error), args ...any) (bool, error) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/go-pg/pg/v10"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/datastore/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -30,6 +31,18 @@ type PostgreSQLConnection struct {
|
||||
connection ConnectionEndpoint
|
||||
}
|
||||
|
||||
func NewPostgreSQLConnection(config ConnectionConfig) (Connection, error) {
|
||||
opt := &pg.Options{
|
||||
Addr: config.Endpoints[0].String(),
|
||||
Database: config.DBName,
|
||||
User: config.User,
|
||||
Password: config.Password,
|
||||
TLSConfig: config.TLSConfig,
|
||||
}
|
||||
|
||||
return &PostgreSQLConnection{db: pg.Connect(opt), connection: config.Endpoints[0]}, nil
|
||||
}
|
||||
|
||||
func (r *PostgreSQLConnection) Driver() string {
|
||||
return string(kamajiv1alpha1.KinePostgreSQLDriver)
|
||||
}
|
||||
@@ -37,7 +50,7 @@ func (r *PostgreSQLConnection) Driver() string {
|
||||
func (r *PostgreSQLConnection) UserExists(ctx context.Context, user string) (bool, error) {
|
||||
res, err := r.db.ExecContext(ctx, postgresqlUserExists, user)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, errors.NewCheckUserExistsError(err)
|
||||
}
|
||||
|
||||
return res.RowsReturned() > 0, nil
|
||||
@@ -46,7 +59,7 @@ func (r *PostgreSQLConnection) UserExists(ctx context.Context, user string) (boo
|
||||
func (r *PostgreSQLConnection) CreateUser(ctx context.Context, user, password string) error {
|
||||
_, err := r.db.ExecContext(ctx, fmt.Sprintf(postgresqlCreateUserStatement, user), password)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.NewCreateUserError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -55,7 +68,7 @@ func (r *PostgreSQLConnection) CreateUser(ctx context.Context, user, password st
|
||||
func (r *PostgreSQLConnection) DBExists(ctx context.Context, dbName string) (bool, error) {
|
||||
rows, err := r.db.ExecContext(ctx, postgresqlFetchDBStatement, dbName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, errors.NewCheckDatabaseExistError(err)
|
||||
}
|
||||
|
||||
return rows.RowsReturned() > 0, nil
|
||||
@@ -64,7 +77,7 @@ func (r *PostgreSQLConnection) DBExists(ctx context.Context, dbName string) (boo
|
||||
func (r *PostgreSQLConnection) CreateDB(ctx context.Context, dbName string) error {
|
||||
_, err := r.db.ExecContext(ctx, fmt.Sprintf(postgresqlCreateDBStatement, dbName))
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.NewCreateDBError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -79,35 +92,42 @@ func (r *PostgreSQLConnection) GrantPrivilegesExists(ctx context.Context, user,
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
return false, errors.NewCheckGrantExistsError(err)
|
||||
}
|
||||
|
||||
return hasDatabasePrivilege == "t", nil
|
||||
}
|
||||
|
||||
func (r *PostgreSQLConnection) GrantPrivileges(ctx context.Context, user, dbName string) error {
|
||||
res, err := r.db.ExecContext(ctx, fmt.Sprintf(postgresqlGrantPrivilegesStatement, dbName, user))
|
||||
_ = res
|
||||
if _, err := r.db.ExecContext(ctx, fmt.Sprintf(postgresqlGrantPrivilegesStatement, dbName, user)); err != nil {
|
||||
return errors.NewGrantPrivilegesError(err)
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *PostgreSQLConnection) DeleteUser(ctx context.Context, user string) error {
|
||||
_, err := r.db.ExecContext(ctx, fmt.Sprintf(postgresqlDropRoleStatement, user))
|
||||
if _, err := r.db.ExecContext(ctx, fmt.Sprintf(postgresqlDropRoleStatement, user)); err != nil {
|
||||
return errors.NewDeleteUserError(err)
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *PostgreSQLConnection) DeleteDB(ctx context.Context, dbName string) error {
|
||||
_, err := r.db.ExecContext(ctx, fmt.Sprintf(postgresqlDropDBStatement, dbName))
|
||||
if _, err := r.db.ExecContext(ctx, fmt.Sprintf(postgresqlDropDBStatement, dbName)); err != nil {
|
||||
return errors.NewCannotDeleteDatabaseError(err)
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *PostgreSQLConnection) RevokePrivileges(ctx context.Context, user, dbName string) error {
|
||||
_, err := r.db.ExecContext(ctx, fmt.Sprintf(postgresqlRevokePrivilegesStatement, dbName, user))
|
||||
if _, err := r.db.ExecContext(ctx, fmt.Sprintf(postgresqlRevokePrivilegesStatement, dbName, user)); err != nil {
|
||||
return errors.NewRevokePrivilegesError(err)
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *PostgreSQLConnection) GetConnectionString() string {
|
||||
@@ -115,9 +135,17 @@ func (r *PostgreSQLConnection) GetConnectionString() string {
|
||||
}
|
||||
|
||||
func (r *PostgreSQLConnection) Close() error {
|
||||
return r.db.Close()
|
||||
if err := r.db.Close(); err != nil {
|
||||
return errors.NewCloseConnectionError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *PostgreSQLConnection) Check(ctx context.Context) error {
|
||||
return r.db.Ping(ctx)
|
||||
if err := r.db.Ping(ctx); err != nil {
|
||||
return errors.NewCheckConnectionError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/clastix/kamaji/internal/crypto"
|
||||
)
|
||||
|
||||
func GetETCDCACertificateAndKeyPair(tenant string, caCert []byte, caPrivKey []byte) (*bytes.Buffer, *bytes.Buffer, error) {
|
||||
template := getCertTemplate(tenant)
|
||||
|
||||
return crypto.GetCertificateAndKeyPair(template, caCert, caPrivKey)
|
||||
}
|
||||
|
||||
func IsETCDCertificateAndKeyPairValid(cert []byte, privKey []byte) (bool, error) {
|
||||
return crypto.IsValidCertificateKeyPairBytes(cert, privKey)
|
||||
}
|
||||
|
||||
func getCertTemplate(tenant string) *x509.Certificate {
|
||||
serialNumber := big.NewInt(rand.Int63())
|
||||
|
||||
return &x509.Certificate{
|
||||
PublicKeyAlgorithm: x509.RSA,
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: tenant,
|
||||
Organization: []string{certOrganization},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(certExpirationDelayYears, 0, 0),
|
||||
SubjectKeyId: []byte{1, 2, 3, 4, 6},
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{
|
||||
x509.ExtKeyUsageClientAuth,
|
||||
x509.ExtKeyUsageServerAuth,
|
||||
x509.ExtKeyUsageCodeSigning,
|
||||
},
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package etcd
|
||||
|
||||
const (
|
||||
certExpirationDelayYears = 10
|
||||
certOrganization = "system:masters"
|
||||
)
|
||||
@@ -5,25 +5,13 @@ package kubeadm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
"io"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api/v1"
|
||||
"k8s.io/component-base/config/v1alpha1"
|
||||
kubeproxyconfig "k8s.io/kube-proxy/config/v1alpha1"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/proxy"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -34,7 +22,14 @@ const (
|
||||
)
|
||||
|
||||
func AddCoreDNS(client kubernetes.Interface, config *Configuration) error {
|
||||
return dns.EnsureDNSAddon(&config.InitConfiguration.ClusterConfiguration, client)
|
||||
// We're passing the values from the parameters here because they wouldn't be hashed by the YAML encoder:
|
||||
// the struct kubeadm.ClusterConfiguration hasn't struct tags, and it wouldn't be hashed properly.
|
||||
if opts := config.Parameters.CoreDNSOptions; opts != nil {
|
||||
config.InitConfiguration.DNS.ImageRepository = opts.Repository
|
||||
config.InitConfiguration.DNS.ImageTag = opts.Tag
|
||||
}
|
||||
|
||||
return dns.EnsureDNSAddon(&config.InitConfiguration.ClusterConfiguration, client, io.Discard, false)
|
||||
}
|
||||
|
||||
func RemoveCoreDNSAddon(ctx context.Context, client kubernetes.Interface) error {
|
||||
@@ -104,24 +99,15 @@ func getCoreDNSConfigMapName(ctx context.Context) (string, error) {
|
||||
return coreDNSName, nil
|
||||
}
|
||||
|
||||
func AddKubeProxy(client kubernetes.Interface, config *Configuration) error {
|
||||
if err := proxy.CreateServiceAccount(client); err != nil {
|
||||
return errors.Wrap(err, "error when creating kube-proxy service account")
|
||||
}
|
||||
func AddKubeProxy(client kubernetes.Interface, config *Configuration) (err error) {
|
||||
// This is a workaround since the function EnsureProxyAddon is picking repository and tag from the InitConfiguration
|
||||
// struct, although is counterintuitive
|
||||
config.InitConfiguration.ClusterConfiguration.CIImageRepository = config.Parameters.KubeProxyOptions.Repository
|
||||
config.InitConfiguration.KubernetesVersion = config.Parameters.KubeProxyOptions.Tag
|
||||
|
||||
if err := createKubeProxyConfigMap(client, config); err != nil {
|
||||
return err
|
||||
}
|
||||
err = proxy.EnsureProxyAddon(&config.InitConfiguration.ClusterConfiguration, &config.InitConfiguration.LocalAPIEndpoint, client, io.Discard, false)
|
||||
|
||||
if err := createKubeProxyAddon(client); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := proxy.CreateRBACRules(client); err != nil {
|
||||
return errors.Wrap(err, "error when creating kube-proxy RBAC rules")
|
||||
}
|
||||
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
func RemoveKubeProxy(ctx context.Context, client kubernetes.Interface) error {
|
||||
@@ -215,247 +201,3 @@ func getKubeProxyConfigMapName(ctx context.Context) (string, error) {
|
||||
// Implement a method for future approaches
|
||||
return kubeProxyName, nil
|
||||
}
|
||||
|
||||
func createKubeProxyConfigMap(client kubernetes.Interface, config *Configuration) error {
|
||||
configConf, err := getKubeproxyConfigmapContent(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kubeconfigConf, err := getKubeproxyKubeconfigContent(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configMap := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: kubeadmconstants.KubeProxyConfigMap,
|
||||
Namespace: "kube-system",
|
||||
Labels: map[string]string{
|
||||
"app": "kube-proxy",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
kubeadmconstants.KubeProxyConfigMapKey: string(configConf),
|
||||
"kubeconfig.conf": string(kubeconfigConf),
|
||||
},
|
||||
}
|
||||
|
||||
return apiclient.CreateOrUpdateConfigMap(client, configMap)
|
||||
}
|
||||
|
||||
func createKubeProxyAddon(client kubernetes.Interface) error {
|
||||
daemonSet := &appsv1.DaemonSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "kube-proxy",
|
||||
Namespace: "kube-system",
|
||||
Labels: map[string]string{
|
||||
"k8s-app": "kube-proxy",
|
||||
},
|
||||
},
|
||||
Spec: appsv1.DaemonSetSpec{
|
||||
RevisionHistoryLimit: pointer.Int32(10),
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"k8s-app": "kube-proxy",
|
||||
},
|
||||
},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"k8s-app": "kube-proxy",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Command: []string{
|
||||
"/usr/local/bin/kube-proxy",
|
||||
"--config=/var/lib/kube-proxy/config.conf",
|
||||
"--hostname-override=$(NODE_NAME)",
|
||||
},
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: "NODE_NAME",
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
FieldRef: &corev1.ObjectFieldSelector{
|
||||
APIVersion: "v1",
|
||||
FieldPath: "spec.nodeName",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Image: "k8s.gcr.io/kube-proxy:v1.21.2",
|
||||
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||
Name: "kube-proxy",
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Privileged: pointer.Bool(true),
|
||||
},
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
TerminationMessagePolicy: "File",
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{
|
||||
MountPath: "/var/lib/kube-proxy",
|
||||
Name: "kube-proxy",
|
||||
},
|
||||
{
|
||||
MountPath: "/run/xtables.lock",
|
||||
Name: "xtables-lock",
|
||||
},
|
||||
{
|
||||
MountPath: "/lib/modules",
|
||||
Name: "lib-modules",
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
DNSPolicy: corev1.DNSClusterFirst,
|
||||
HostNetwork: true,
|
||||
NodeSelector: map[string]string{
|
||||
"kubernetes.io/os": "linux",
|
||||
},
|
||||
Tolerations: []corev1.Toleration{
|
||||
{Operator: corev1.TolerationOpExists},
|
||||
},
|
||||
PriorityClassName: "system-node-critical",
|
||||
RestartPolicy: corev1.RestartPolicyAlways,
|
||||
SchedulerName: "default-scheduler",
|
||||
ServiceAccountName: "kube-proxy",
|
||||
TerminationGracePeriodSeconds: pointer.Int64(30),
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "kube-proxy",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
DefaultMode: pointer.Int32(420),
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "kube-proxy",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "xtables-lock",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
HostPath: &corev1.HostPathVolumeSource{
|
||||
Path: "/run/xtables.lock",
|
||||
Type: (*corev1.HostPathType)(pointer.String(string(corev1.HostPathFileOrCreate))),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "lib-modules",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
HostPath: &corev1.HostPathVolumeSource{
|
||||
Path: "/lib/modules",
|
||||
Type: (*corev1.HostPathType)(pointer.String(string(corev1.HostPathUnset))),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return apiclient.CreateOrUpdateDaemonSet(client, daemonSet)
|
||||
}
|
||||
|
||||
func getKubeproxyConfigmapContent(config *Configuration) ([]byte, error) {
|
||||
zeroDuration := metav1.Duration{Duration: 0}
|
||||
oneSecondDuration := metav1.Duration{Duration: time.Second}
|
||||
|
||||
kubeProxyConfiguration := kubeproxyconfig.KubeProxyConfiguration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "KubeProxyConfiguration",
|
||||
APIVersion: "kubeproxy.config.k8s.io/v1alpha1",
|
||||
},
|
||||
BindAddress: "0.0.0.0",
|
||||
BindAddressHardFail: false,
|
||||
ClientConnection: v1alpha1.ClientConnectionConfiguration{
|
||||
AcceptContentTypes: "",
|
||||
Burst: 0,
|
||||
ContentType: "",
|
||||
Kubeconfig: "/var/lib/kube-proxy/kubeconfig.conf",
|
||||
QPS: 0,
|
||||
},
|
||||
ClusterCIDR: config.Parameters.TenantControlPlanePodCIDR,
|
||||
ConfigSyncPeriod: zeroDuration,
|
||||
Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
|
||||
MaxPerCore: pointer.Int32(0),
|
||||
Min: nil,
|
||||
TCPCloseWaitTimeout: nil,
|
||||
TCPEstablishedTimeout: nil,
|
||||
},
|
||||
DetectLocalMode: "",
|
||||
EnableProfiling: false,
|
||||
HealthzBindAddress: "",
|
||||
HostnameOverride: "",
|
||||
IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
|
||||
MasqueradeAll: false,
|
||||
MasqueradeBit: nil,
|
||||
MinSyncPeriod: oneSecondDuration,
|
||||
SyncPeriod: zeroDuration,
|
||||
},
|
||||
IPVS: kubeproxyconfig.KubeProxyIPVSConfiguration{
|
||||
ExcludeCIDRs: nil,
|
||||
MinSyncPeriod: zeroDuration,
|
||||
Scheduler: "",
|
||||
StrictARP: false,
|
||||
SyncPeriod: zeroDuration,
|
||||
TCPTimeout: zeroDuration,
|
||||
TCPFinTimeout: zeroDuration,
|
||||
UDPTimeout: zeroDuration,
|
||||
},
|
||||
MetricsBindAddress: "",
|
||||
Mode: "iptables",
|
||||
NodePortAddresses: nil,
|
||||
OOMScoreAdj: nil,
|
||||
PortRange: "",
|
||||
ShowHiddenMetricsForVersion: "",
|
||||
UDPIdleTimeout: zeroDuration,
|
||||
Winkernel: kubeproxyconfig.KubeProxyWinkernelConfiguration{
|
||||
EnableDSR: false,
|
||||
NetworkName: "",
|
||||
SourceVip: "",
|
||||
},
|
||||
}
|
||||
|
||||
return utilities.EncondeToYaml(&kubeProxyConfiguration)
|
||||
}
|
||||
|
||||
func getKubeproxyKubeconfigContent(config *Configuration) ([]byte, error) {
|
||||
kubeconfig := clientcmdapi.Config{
|
||||
APIVersion: "v1",
|
||||
Kind: "Config",
|
||||
Clusters: []clientcmdapi.NamedCluster{
|
||||
{
|
||||
Name: "default",
|
||||
Cluster: clientcmdapi.Cluster{
|
||||
CertificateAuthority: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
|
||||
Server: fmt.Sprintf("https://%s:%d", config.Parameters.TenantControlPlaneAddress, config.Parameters.TenantControlPlanePort),
|
||||
},
|
||||
},
|
||||
},
|
||||
Contexts: []clientcmdapi.NamedContext{
|
||||
{
|
||||
Context: clientcmdapi.Context{
|
||||
Cluster: "default",
|
||||
Namespace: "default",
|
||||
AuthInfo: "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
AuthInfos: []clientcmdapi.NamedAuthInfo{
|
||||
{
|
||||
Name: "default",
|
||||
AuthInfo: clientcmdapi.AuthInfo{
|
||||
TokenFile: "/var/run/secrets/kubernetes.io/serviceaccount/token",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return utilities.EncondeToYaml(&kubeconfig)
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ func GenerateCACertificatePrivateKeyPair(baseName string, config *Configuration)
|
||||
func GenerateCertificatePrivateKeyPair(baseName string, config *Configuration, ca CertificatePrivateKeyPair) (*CertificatePrivateKeyPair, error) {
|
||||
defer deleteCertificateDirectory(config.InitConfiguration.CertificatesDir)
|
||||
|
||||
certificate, _ := cryptoKamaji.GetCertificate(ca.Certificate)
|
||||
signer, _ := cryptoKamaji.GetPrivateKey(ca.PrivateKey)
|
||||
certificate, _ := cryptoKamaji.ParseCertificateBytes(ca.Certificate)
|
||||
signer, _ := cryptoKamaji.ParsePrivateKeyBytes(ca.PrivateKey)
|
||||
|
||||
kubeadmCert, err := getKubeadmCert(baseName)
|
||||
if err != nil {
|
||||
@@ -106,28 +106,6 @@ func GeneratePublicKeyPrivateKeyPair(baseName string, config *Configuration) (*P
|
||||
return publicKeyPrivateKeyPair, err
|
||||
}
|
||||
|
||||
func IsCertificatePrivateKeyPairValid(certificate []byte, privKey []byte) (bool, error) {
|
||||
if len(certificate) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
if len(privKey) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return cryptoKamaji.IsValidCertificateKeyPairBytes(certificate, privKey)
|
||||
}
|
||||
|
||||
func IsPublicKeyPrivateKeyPairValid(pubKey []byte, privKey []byte) (bool, error) {
|
||||
if len(pubKey) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
if len(privKey) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return cryptoKamaji.IsValidKeyPairBytes(pubKey, privKey)
|
||||
}
|
||||
|
||||
func initPhaseCertsSA(config *Configuration) error {
|
||||
return certs.CreateServiceAccountKeyAndPublicKeyFiles(config.InitConfiguration.CertificatesDir, config.InitConfiguration.PublicKeyAlgorithm())
|
||||
}
|
||||
|
||||
@@ -4,15 +4,14 @@
|
||||
package kubeadm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
||||
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -21,128 +20,103 @@ const (
|
||||
defaultKeyFile = "/etc/kubernetes/pki/apiserver-etcd-client.key"
|
||||
)
|
||||
|
||||
func CreateKubeadmInitConfiguration(params Parameters) Configuration {
|
||||
config := kubeadmapi.InitConfiguration{
|
||||
ClusterConfiguration: getKubeadmClusterConfiguration(params),
|
||||
BootstrapTokens: []bootstraptokenv1.BootstrapToken{
|
||||
{
|
||||
Groups: []string{"system:bootstrappers:kubeadm:default-node-token"},
|
||||
TTL: &metav1.Duration{Duration: 48 * time.Hour},
|
||||
Usages: []string{
|
||||
"signing",
|
||||
"authentication",
|
||||
},
|
||||
},
|
||||
},
|
||||
LocalAPIEndpoint: kubeadmapi.APIEndpoint{
|
||||
AdvertiseAddress: params.TenantControlPlaneAddress,
|
||||
BindPort: params.TenantControlPlanePort,
|
||||
},
|
||||
NodeRegistration: kubeadmapi.NodeRegistrationOptions{
|
||||
CRISocket: "unix:///run/containerd/containerd.sock",
|
||||
Name: params.TenantControlPlaneName,
|
||||
},
|
||||
func CreateKubeadmInitConfiguration(params Parameters) (*Configuration, error) {
|
||||
defaultConf, err := config.DefaultedStaticInitConfiguration()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return Configuration{InitConfiguration: config}
|
||||
}
|
||||
conf := defaultConf
|
||||
// Due to unmarshaling error when GetKubeadmInitConfigurationFromMap function is issued,
|
||||
// we have to store the ComponentConfigs to a null value.
|
||||
conf.ClusterConfiguration.ComponentConfigs = nil
|
||||
|
||||
func isHTTPS(url string) bool {
|
||||
return strings.HasPrefix(url, "https")
|
||||
}
|
||||
conf.LocalAPIEndpoint = kubeadmapi.APIEndpoint{
|
||||
AdvertiseAddress: params.TenantControlPlaneAddress,
|
||||
BindPort: params.TenantControlPlanePort,
|
||||
}
|
||||
conf.NodeRegistration.Name = params.TenantControlPlaneName
|
||||
|
||||
func getKubeadmClusterConfiguration(params Parameters) kubeadmapi.ClusterConfiguration {
|
||||
caFile, certFile, keyFile := "", "", ""
|
||||
if isHTTPS(params.ETCDs[0]) {
|
||||
if strings.HasPrefix(params.ETCDs[0], "https") {
|
||||
caFile, certFile, keyFile = defaultCAFile, defaultCertFile, defaultKeyFile
|
||||
}
|
||||
|
||||
return kubeadmapi.ClusterConfiguration{
|
||||
KubernetesVersion: params.TenantControlPlaneVersion,
|
||||
ClusterName: params.TenantControlPlaneName,
|
||||
CertificatesDir: "/etc/kubernetes/pki",
|
||||
ImageRepository: "k8s.gcr.io",
|
||||
Networking: kubeadmapi.Networking{
|
||||
DNSDomain: "cluster.local",
|
||||
PodSubnet: params.TenantControlPlanePodCIDR,
|
||||
ServiceSubnet: params.TenantControlPlaneServiceCIDR,
|
||||
},
|
||||
DNS: kubeadmapi.DNS{
|
||||
Type: "CoreDNS",
|
||||
},
|
||||
ControlPlaneEndpoint: params.TenantControlPlaneEndpoint,
|
||||
Etcd: kubeadmapi.Etcd{
|
||||
External: &kubeadmapi.ExternalEtcd{
|
||||
Endpoints: params.ETCDs,
|
||||
CAFile: caFile,
|
||||
CertFile: certFile,
|
||||
KeyFile: keyFile,
|
||||
},
|
||||
},
|
||||
APIServer: kubeadmapi.APIServer{
|
||||
CertSANs: append([]string{
|
||||
"127.0.0.1",
|
||||
"localhost",
|
||||
params.TenantControlPlaneName,
|
||||
fmt.Sprintf("%s.%s.svc", params.TenantControlPlaneName, params.TenantControlPlaneNamespace),
|
||||
fmt.Sprintf("%s.%s.svc.cluster.local", params.TenantControlPlaneName, params.TenantControlPlaneNamespace),
|
||||
params.TenantControlPlaneAddress,
|
||||
}, params.TenantControlPlaneCertSANs...),
|
||||
ControlPlaneComponent: kubeadmapi.ControlPlaneComponent{
|
||||
ExtraArgs: map[string]string{
|
||||
"etcd-compaction-interval": "0s",
|
||||
"etcd-prefix": fmt.Sprintf("/%s", params.TenantControlPlaneName),
|
||||
},
|
||||
},
|
||||
conf.Etcd = kubeadmapi.Etcd{
|
||||
External: &kubeadmapi.ExternalEtcd{
|
||||
Endpoints: params.ETCDs,
|
||||
CAFile: caFile,
|
||||
CertFile: certFile,
|
||||
KeyFile: keyFile,
|
||||
},
|
||||
}
|
||||
conf.Networking = kubeadmapi.Networking{
|
||||
DNSDomain: "cluster.local",
|
||||
PodSubnet: params.TenantControlPlanePodCIDR,
|
||||
ServiceSubnet: params.TenantControlPlaneServiceCIDR,
|
||||
}
|
||||
conf.KubernetesVersion = params.TenantControlPlaneVersion
|
||||
conf.ControlPlaneEndpoint = params.TenantControlPlaneEndpoint
|
||||
conf.APIServer.CertSANs = append([]string{
|
||||
"127.0.0.1",
|
||||
"localhost",
|
||||
params.TenantControlPlaneName,
|
||||
fmt.Sprintf("%s.%s.svc", params.TenantControlPlaneName, params.TenantControlPlaneNamespace),
|
||||
fmt.Sprintf("%s.%s.svc.cluster.local", params.TenantControlPlaneName, params.TenantControlPlaneNamespace),
|
||||
params.TenantControlPlaneAddress,
|
||||
}, params.TenantControlPlaneCertSANs...)
|
||||
conf.APIServer.ControlPlaneComponent.ExtraArgs = map[string]string{
|
||||
"etcd-compaction-interval": "0s",
|
||||
"etcd-prefix": fmt.Sprintf("/%s", params.TenantControlPlaneName),
|
||||
}
|
||||
conf.ClusterName = params.TenantControlPlaneName
|
||||
|
||||
return &Configuration{InitConfiguration: *conf}, nil
|
||||
}
|
||||
|
||||
func GetKubeadmInitConfigurationMap(config Configuration) (map[string]string, error) {
|
||||
initConfigurationString, err := getJSONStringFromStruct(config.InitConfiguration)
|
||||
initConfigurationString, err := utilities.EncodeToJSON(&config.InitConfiguration)
|
||||
if err != nil {
|
||||
return map[string]string{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clusterConfigurationString, err := getJSONStringFromStruct(config.InitConfiguration.ClusterConfiguration)
|
||||
clusterConfigurationString, err := utilities.EncodeToJSON(&config.InitConfiguration.ClusterConfiguration)
|
||||
if err != nil {
|
||||
return map[string]string{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string]string{
|
||||
kubeadmconstants.InitConfigurationKind: initConfigurationString,
|
||||
kubeadmconstants.ClusterConfigurationKind: clusterConfigurationString,
|
||||
kubeadmconstants.InitConfigurationKind: string(initConfigurationString),
|
||||
kubeadmconstants.ClusterConfigurationKind: string(clusterConfigurationString),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetKubeadmInitConfigurationFromMap(config map[string]string) (*Configuration, error) {
|
||||
initConfigurationString, ok := config[kubeadmconstants.InitConfigurationKind]
|
||||
func GetKubeadmInitConfigurationFromMap(conf map[string]string) (*Configuration, error) {
|
||||
initConfigurationString, ok := conf[kubeadmconstants.InitConfigurationKind]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s is not in the map", kubeadmconstants.InitConfigurationKind)
|
||||
}
|
||||
|
||||
clusterConfigurationString, ok := config[kubeadmconstants.ClusterConfigurationKind]
|
||||
clusterConfigurationString, ok := conf[kubeadmconstants.ClusterConfigurationKind]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s is not in the map", kubeadmconstants.ClusterConfigurationKind)
|
||||
}
|
||||
|
||||
initConfiguration := kubeadmapi.InitConfiguration{}
|
||||
if err := json.Unmarshal([]byte(initConfigurationString), &initConfiguration); err != nil {
|
||||
if err := utilities.DecodeFromJSON(initConfigurationString, &initConfiguration); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(clusterConfigurationString), &initConfiguration.ClusterConfiguration); err != nil {
|
||||
if err := utilities.DecodeFromJSON(clusterConfigurationString, &initConfiguration.ClusterConfiguration); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Due to some weird issues with unmarshaling of the ComponentConfigs struct,
|
||||
// we have to extract the default value and assign it directly.
|
||||
defaults, err := config.DefaultedStaticInitConfiguration()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
initConfiguration.ClusterConfiguration.ComponentConfigs = defaults.ComponentConfigs
|
||||
|
||||
return &Configuration{InitConfiguration: initConfiguration}, nil
|
||||
}
|
||||
|
||||
func getJSONStringFromStruct(i interface{}) (string, error) {
|
||||
b, err := json.Marshal(i)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
38
internal/kubeadm/printers/discard.go
Normal file
38
internal/kubeadm/printers/discard.go
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package printers
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
type Discard struct{}
|
||||
|
||||
func (d Discard) PrintObj(obj runtime.Object, writer io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d Discard) Fprintf(writer io.Writer, format string, args ...interface{}) (n int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d Discard) Fprintln(writer io.Writer, args ...interface{}) (n int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d Discard) Printf(format string, args ...interface{}) (n int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d Discard) Println(args ...interface{}) (n int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d Discard) Flush(writer io.Writer, last bool) {
|
||||
}
|
||||
|
||||
func (d Discard) Close(writer io.Writer) {
|
||||
}
|
||||
@@ -5,30 +5,30 @@ package kubeadm
|
||||
|
||||
import (
|
||||
json "github.com/json-iterator/go"
|
||||
clientcmdapiv1 "k8s.io/client-go/tools/clientcmd/api/v1"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
|
||||
kubeconfigutil "github.com/clastix/kamaji/internal/kubeconfig"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
type Configuration struct {
|
||||
InitConfiguration kubeadmapi.InitConfiguration
|
||||
Kubeconfig kubeconfigutil.Kubeconfig
|
||||
Kubeconfig clientcmdapiv1.Config
|
||||
Parameters Parameters
|
||||
}
|
||||
|
||||
func (c *Configuration) Checksum() string {
|
||||
initConfiguration, _ := utilities.EncondeToYaml(&c.InitConfiguration)
|
||||
initConfiguration, _ := utilities.EncodeToYaml(&c.InitConfiguration)
|
||||
kubeconfig, _ := json.Marshal(c.Kubeconfig)
|
||||
parameters, _ := json.Marshal(c.Parameters)
|
||||
|
||||
data := map[string]string{
|
||||
"InitConfiguration": string(initConfiguration),
|
||||
"Kubeconfig": string(kubeconfig),
|
||||
"Parameters": string(parameters),
|
||||
data := map[string][]byte{
|
||||
"InitConfiguration": initConfiguration,
|
||||
"Kubeconfig": kubeconfig,
|
||||
"Parameters": parameters,
|
||||
}
|
||||
|
||||
return utilities.CalculateConfigMapChecksum(data)
|
||||
return utilities.CalculateMapChecksum(data)
|
||||
}
|
||||
|
||||
type Parameters struct {
|
||||
@@ -46,6 +46,13 @@ type Parameters struct {
|
||||
ETCDs []string
|
||||
CertificatesDir string
|
||||
KubeconfigDir string
|
||||
KubeProxyOptions *AddonOptions
|
||||
CoreDNSOptions *AddonOptions
|
||||
}
|
||||
|
||||
type AddonOptions struct {
|
||||
Repository string
|
||||
Tag string
|
||||
}
|
||||
|
||||
type KubeletConfiguration struct {
|
||||
|
||||
@@ -4,13 +4,10 @@
|
||||
package kubeadm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
k8sversion "k8s.io/apimachinery/pkg/util/version"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
kubelettypes "k8s.io/kubelet/config/v1beta1"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
@@ -37,10 +34,7 @@ func UploadKubeletConfig(client kubernetes.Interface, config *Configuration) err
|
||||
return err
|
||||
}
|
||||
|
||||
configMapName, err := configMapName(config.Parameters.TenantControlPlaneVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configMapName := kubeadmconstants.KubeletBaseConfigurationConfigMap
|
||||
|
||||
configMap := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -56,7 +50,7 @@ func UploadKubeletConfig(client kubernetes.Interface, config *Configuration) err
|
||||
return err
|
||||
}
|
||||
|
||||
if err := createConfigMapRBACRules(client, config.Parameters.TenantControlPlaneVersion); err != nil {
|
||||
if err := createConfigMapRBACRules(client); err != nil {
|
||||
return errors.Wrap(err, "error creating kubelet configuration configmap RBAC rules")
|
||||
}
|
||||
|
||||
@@ -117,19 +111,11 @@ func getKubeletConfigmapContent(kubeletConfiguration KubeletConfiguration) ([]by
|
||||
VolumeStatsAggPeriod: zeroDuration,
|
||||
}
|
||||
|
||||
return utilities.EncondeToYaml(&kc)
|
||||
return utilities.EncodeToYaml(&kc)
|
||||
}
|
||||
|
||||
func createConfigMapRBACRules(client kubernetes.Interface, kubernetesVersion string) error {
|
||||
configMapName, err := configMapName(kubernetesVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configMapRBACName, err := configMapRBACName(kubernetesVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func createConfigMapRBACRules(client kubernetes.Interface) error {
|
||||
configMapRBACName := kubeadmconstants.KubeletBaseConfigMapRole
|
||||
|
||||
if err := apiclient.CreateOrUpdateRole(client, &rbacv1.Role{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -141,7 +127,7 @@ func createConfigMapRBACRules(client kubernetes.Interface, kubernetesVersion str
|
||||
Verbs: []string{"get"},
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"configmaps"},
|
||||
ResourceNames: []string{configMapName},
|
||||
ResourceNames: []string{kubeadmconstants.KubeletBaseConfigurationConfigMap},
|
||||
},
|
||||
},
|
||||
}); err != nil {
|
||||
@@ -170,21 +156,3 @@ func createConfigMapRBACRules(client kubernetes.Interface, kubernetesVersion str
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func configMapName(kubernetesVersion string) (string, error) {
|
||||
version, err := k8sversion.ParseSemantic(kubernetesVersion)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return kubeadmconstants.GetKubeletConfigMapName(version, true), nil
|
||||
}
|
||||
|
||||
func configMapRBACName(kubernetesVersion string) (string, error) {
|
||||
version, err := k8sversion.ParseSemantic(kubernetesVersion)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s%d.%d", kubeadmconstants.KubeletBaseConfigMapRolePrefix, version.Major(), version.Minor()), nil
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package kubeconfig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
v1 "k8s.io/client-go/tools/clientcmd/api/v1"
|
||||
)
|
||||
|
||||
type Kubeconfig v1.Config
|
||||
|
||||
func GetKubeconfigFromBytesBuffer(buffer *bytes.Buffer) (*Kubeconfig, error) {
|
||||
kubeconfig := &Kubeconfig{}
|
||||
if err := yaml.NewYAMLOrJSONDecoder(buffer, buffer.Len()).Decode(kubeconfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return kubeconfig, nil
|
||||
}
|
||||
|
||||
func GetKubeconfigFromBytes(b []byte) (*Kubeconfig, error) {
|
||||
buffer := bytes.NewBuffer(b)
|
||||
|
||||
return GetKubeconfigFromBytesBuffer(buffer)
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
@@ -15,8 +14,11 @@ import (
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/constants"
|
||||
"github.com/clastix/kamaji/internal/crypto"
|
||||
"github.com/clastix/kamaji/internal/kubeadm"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
@@ -24,12 +26,11 @@ import (
|
||||
type APIServerCertificate struct {
|
||||
resource *corev1.Secret
|
||||
Client client.Client
|
||||
Log logr.Logger
|
||||
TmpDirectory string
|
||||
}
|
||||
|
||||
func (r *APIServerCertificate) ShouldStatusBeUpdated(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return tenantControlPlane.Status.Certificates.APIServer.Checksum != r.resource.GetAnnotations()["checksum"]
|
||||
return tenantControlPlane.Status.Certificates.APIServer.Checksum != r.resource.GetAnnotations()[constants.Checksum]
|
||||
}
|
||||
|
||||
func (r *APIServerCertificate) ShouldCleanup(_ *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
@@ -74,20 +75,22 @@ func (r *APIServerCertificate) GetName() string {
|
||||
func (r *APIServerCertificate) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
tenantControlPlane.Status.Certificates.APIServer.LastUpdate = metav1.Now()
|
||||
tenantControlPlane.Status.Certificates.APIServer.SecretName = r.resource.GetName()
|
||||
tenantControlPlane.Status.Certificates.APIServer.Checksum = r.resource.GetAnnotations()["checksum"]
|
||||
tenantControlPlane.Status.Certificates.APIServer.Checksum = r.resource.GetAnnotations()[constants.Checksum]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *APIServerCertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
|
||||
return func() error {
|
||||
if checksum := tenantControlPlane.Status.Certificates.APIServer.Checksum; len(checksum) > 0 && checksum == r.resource.GetAnnotations()["checksum"] {
|
||||
isValid, err := kubeadm.IsCertificatePrivateKeyPairValid(
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
if checksum := tenantControlPlane.Status.Certificates.APIServer.Checksum; len(checksum) > 0 && checksum == r.resource.GetAnnotations()[constants.Checksum] {
|
||||
isValid, err := crypto.CheckCertificateAndPrivateKeyPairValidity(
|
||||
r.resource.Data[kubeadmconstants.APIServerCertName],
|
||||
r.resource.Data[kubeadmconstants.APIServerKeyName],
|
||||
)
|
||||
if err != nil {
|
||||
r.Log.Info(fmt.Sprintf("%s certificate-private_key pair is not valid: %s", kubeadmconstants.APIServerCertAndKeyBaseName, err.Error()))
|
||||
logger.Info(fmt.Sprintf("%s certificate-private_key pair is not valid: %s", kubeadmconstants.APIServerCertAndKeyBaseName, err.Error()))
|
||||
}
|
||||
if isValid {
|
||||
return nil
|
||||
@@ -96,12 +99,16 @@ func (r *APIServerCertificate) mutate(ctx context.Context, tenantControlPlane *k
|
||||
|
||||
config, err := getStoredKubeadmConfiguration(ctx, r, tenantControlPlane)
|
||||
if err != nil {
|
||||
logger.Error(err, "cannot retrieve kubeadm configuration")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
namespacedName := k8stypes.NamespacedName{Namespace: tenantControlPlane.GetNamespace(), Name: tenantControlPlane.Status.Certificates.CA.SecretName}
|
||||
secretCA := &corev1.Secret{}
|
||||
if err = r.Client.Get(ctx, namespacedName, secretCA); err != nil {
|
||||
logger.Error(err, "cannot retrieve CA secret")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -112,6 +119,8 @@ func (r *APIServerCertificate) mutate(ctx context.Context, tenantControlPlane *k
|
||||
}
|
||||
certificateKeyPair, err := kubeadm.GenerateCertificatePrivateKeyPair(kubeadmconstants.APIServerCertAndKeyBaseName, config, ca)
|
||||
if err != nil {
|
||||
logger.Error(err, "cannot generate certificate and private key")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -124,7 +133,7 @@ func (r *APIServerCertificate) mutate(ctx context.Context, tenantControlPlane *k
|
||||
if annotations == nil {
|
||||
annotations = map[string]string{}
|
||||
}
|
||||
annotations["checksum"] = utilities.CalculateConfigMapChecksum(r.resource.StringData)
|
||||
annotations[constants.Checksum] = utilities.CalculateMapChecksum(r.resource.Data)
|
||||
r.resource.SetAnnotations(annotations)
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
@@ -15,8 +14,11 @@ import (
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/constants"
|
||||
"github.com/clastix/kamaji/internal/crypto"
|
||||
"github.com/clastix/kamaji/internal/kubeadm"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
@@ -24,12 +26,11 @@ import (
|
||||
type APIServerKubeletClientCertificate struct {
|
||||
resource *corev1.Secret
|
||||
Client client.Client
|
||||
Log logr.Logger
|
||||
TmpDirectory string
|
||||
}
|
||||
|
||||
func (r *APIServerKubeletClientCertificate) ShouldStatusBeUpdated(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return tenantControlPlane.Status.Certificates.APIServerKubeletClient.Checksum != r.resource.GetAnnotations()["checksum"]
|
||||
return tenantControlPlane.Status.Certificates.APIServerKubeletClient.Checksum != r.resource.GetAnnotations()[constants.Checksum]
|
||||
}
|
||||
|
||||
func (r *APIServerKubeletClientCertificate) ShouldCleanup(*kamajiv1alpha1.TenantControlPlane) bool {
|
||||
@@ -74,20 +75,22 @@ func (r *APIServerKubeletClientCertificate) GetName() string {
|
||||
func (r *APIServerKubeletClientCertificate) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
tenantControlPlane.Status.Certificates.APIServerKubeletClient.LastUpdate = metav1.Now()
|
||||
tenantControlPlane.Status.Certificates.APIServerKubeletClient.SecretName = r.resource.GetName()
|
||||
tenantControlPlane.Status.Certificates.APIServerKubeletClient.Checksum = r.resource.GetAnnotations()["checksum"]
|
||||
tenantControlPlane.Status.Certificates.APIServerKubeletClient.Checksum = r.resource.GetAnnotations()[constants.Checksum]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *APIServerKubeletClientCertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
|
||||
return func() error {
|
||||
if checksum := tenantControlPlane.Status.Certificates.APIServerKubeletClient.Checksum; len(checksum) > 0 && checksum == r.resource.GetAnnotations()["checksum"] {
|
||||
isValid, err := kubeadm.IsCertificatePrivateKeyPairValid(
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
if checksum := tenantControlPlane.Status.Certificates.APIServerKubeletClient.Checksum; len(checksum) > 0 && checksum == r.resource.GetAnnotations()[constants.Checksum] {
|
||||
isValid, err := crypto.CheckCertificateAndPrivateKeyPairValidity(
|
||||
r.resource.Data[kubeadmconstants.APIServerKubeletClientCertName],
|
||||
r.resource.Data[kubeadmconstants.APIServerKubeletClientKeyName],
|
||||
)
|
||||
if err != nil {
|
||||
r.Log.Info(fmt.Sprintf("%s certificate-private_key pair is not valid: %s", kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName, err.Error()))
|
||||
logger.Info(fmt.Sprintf("%s certificate-private_key pair is not valid: %s", kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName, err.Error()))
|
||||
}
|
||||
if isValid {
|
||||
return nil
|
||||
@@ -96,6 +99,8 @@ func (r *APIServerKubeletClientCertificate) mutate(ctx context.Context, tenantCo
|
||||
|
||||
config, err := getStoredKubeadmConfiguration(ctx, r, tenantControlPlane)
|
||||
if err != nil {
|
||||
logger.Error(err, "cannot retrieve kubeadm configuration")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -103,6 +108,8 @@ func (r *APIServerKubeletClientCertificate) mutate(ctx context.Context, tenantCo
|
||||
|
||||
secretCA := &corev1.Secret{}
|
||||
if err = r.Client.Get(ctx, namespacedName, secretCA); err != nil {
|
||||
logger.Error(err, "cannot retrieve CA secret")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -113,6 +120,8 @@ func (r *APIServerKubeletClientCertificate) mutate(ctx context.Context, tenantCo
|
||||
}
|
||||
certificateKeyPair, err := kubeadm.GenerateCertificatePrivateKeyPair(kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName, config, ca)
|
||||
if err != nil {
|
||||
logger.Error(err, "cannot generate certificate and private key")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -133,7 +142,7 @@ func (r *APIServerKubeletClientCertificate) mutate(ctx context.Context, tenantCo
|
||||
if annotations == nil {
|
||||
annotations = map[string]string{}
|
||||
}
|
||||
annotations["checksum"] = utilities.CalculateConfigMapChecksum(r.resource.StringData)
|
||||
annotations[constants.Checksum] = utilities.CalculateMapChecksum(r.resource.Data)
|
||||
r.resource.SetAnnotations(annotations)
|
||||
|
||||
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
|
||||
|
||||
@@ -7,15 +7,17 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/constants"
|
||||
"github.com/clastix/kamaji/internal/crypto"
|
||||
"github.com/clastix/kamaji/internal/kubeadm"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
@@ -23,13 +25,12 @@ import (
|
||||
type CACertificate struct {
|
||||
resource *corev1.Secret
|
||||
Client client.Client
|
||||
Log logr.Logger
|
||||
TmpDirectory string
|
||||
}
|
||||
|
||||
func (r *CACertificate) ShouldStatusBeUpdated(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return tenantControlPlane.Status.Certificates.CA.SecretName != r.resource.GetName() ||
|
||||
tenantControlPlane.Status.Certificates.CA.Checksum != r.resource.GetAnnotations()["checksum"]
|
||||
tenantControlPlane.Status.Certificates.CA.Checksum != r.resource.GetAnnotations()[constants.Checksum]
|
||||
}
|
||||
|
||||
func (r *CACertificate) ShouldCleanup(*kamajiv1alpha1.TenantControlPlane) bool {
|
||||
@@ -74,20 +75,22 @@ func (r *CACertificate) GetName() string {
|
||||
func (r *CACertificate) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
tenantControlPlane.Status.Certificates.CA.LastUpdate = metav1.Now()
|
||||
tenantControlPlane.Status.Certificates.CA.SecretName = r.resource.GetName()
|
||||
tenantControlPlane.Status.Certificates.CA.Checksum = r.resource.GetAnnotations()["checksum"]
|
||||
tenantControlPlane.Status.Certificates.CA.Checksum = r.resource.GetAnnotations()[constants.Checksum]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *CACertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
|
||||
return func() error {
|
||||
if checksum := tenantControlPlane.Status.Certificates.CA.Checksum; len(checksum) > 0 && checksum == r.resource.GetAnnotations()["checksum"] {
|
||||
isValid, err := kubeadm.IsCertificatePrivateKeyPairValid(
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
if checksum := tenantControlPlane.Status.Certificates.CA.Checksum; len(checksum) > 0 && checksum == r.resource.GetAnnotations()[constants.Checksum] {
|
||||
isValid, err := crypto.CheckCertificateAndPrivateKeyPairValidity(
|
||||
r.resource.Data[kubeadmconstants.CACertName],
|
||||
r.resource.Data[kubeadmconstants.CAKeyName],
|
||||
)
|
||||
if err != nil {
|
||||
r.Log.Info(fmt.Sprintf("%s certificate-private_key pair is not valid: %s", kubeadmconstants.CACertAndKeyBaseName, err.Error()))
|
||||
logger.Info(fmt.Sprintf("%s certificate-private_key pair is not valid: %s", kubeadmconstants.CACertAndKeyBaseName, err.Error()))
|
||||
}
|
||||
if isValid {
|
||||
return nil
|
||||
@@ -96,11 +99,15 @@ func (r *CACertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1
|
||||
|
||||
config, err := getStoredKubeadmConfiguration(ctx, r, tenantControlPlane)
|
||||
if err != nil {
|
||||
logger.Error(err, "cannot retrieve kubeadm configuration")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
ca, err := kubeadm.GenerateCACertificatePrivateKeyPair(kubeadmconstants.CACertAndKeyBaseName, config)
|
||||
if err != nil {
|
||||
logger.Error(err, "cannot generate certificate and private key")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -121,7 +128,7 @@ func (r *CACertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1
|
||||
if annotations == nil {
|
||||
annotations = map[string]string{}
|
||||
}
|
||||
annotations["checksum"] = utilities.CalculateConfigMapChecksum(r.resource.StringData)
|
||||
annotations[constants.Checksum] = utilities.CalculateMapChecksum(r.resource.Data)
|
||||
r.resource.SetAnnotations(annotations)
|
||||
|
||||
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
|
||||
|
||||
@@ -13,9 +13,11 @@ import (
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/etcd"
|
||||
"github.com/clastix/kamaji/internal/constants"
|
||||
"github.com/clastix/kamaji/internal/crypto"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
@@ -27,7 +29,7 @@ type Certificate struct {
|
||||
}
|
||||
|
||||
func (r *Certificate) ShouldStatusBeUpdated(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return tenantControlPlane.Status.Storage.Certificate.Checksum != r.resource.GetAnnotations()["checksum"]
|
||||
return tenantControlPlane.Status.Storage.Certificate.Checksum != r.resource.GetAnnotations()[constants.Checksum]
|
||||
}
|
||||
|
||||
func (r *Certificate) ShouldCleanup(*kamajiv1alpha1.TenantControlPlane) bool {
|
||||
@@ -68,7 +70,7 @@ func (r *Certificate) GetName() string {
|
||||
|
||||
func (r *Certificate) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
tenantControlPlane.Status.Storage.Certificate.SecretName = r.resource.GetName()
|
||||
tenantControlPlane.Status.Storage.Certificate.Checksum = r.resource.GetAnnotations()["checksum"]
|
||||
tenantControlPlane.Status.Storage.Certificate.Checksum = r.resource.GetAnnotations()[constants.Checksum]
|
||||
tenantControlPlane.Status.Storage.Certificate.LastUpdate = metav1.Now()
|
||||
|
||||
return nil
|
||||
@@ -76,16 +78,20 @@ func (r *Certificate) UpdateTenantControlPlaneStatus(_ context.Context, tenantCo
|
||||
|
||||
func (r *Certificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
|
||||
return func() error {
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
ca, err := r.DataStore.Spec.TLSConfig.CertificateAuthority.Certificate.GetContent(ctx, r.Client)
|
||||
if err != nil {
|
||||
logger.Error(err, "cannot retrieve CA certificate content")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
r.resource.Data["ca.crt"] = ca
|
||||
|
||||
if r.resource.GetAnnotations()["checksum"] == utilities.CalculateConfigMapChecksum(r.resource.StringData) {
|
||||
if r.resource.GetAnnotations()[constants.Checksum] == utilities.CalculateMapChecksum(r.resource.Data) {
|
||||
if r.DataStore.Spec.Driver == kamajiv1alpha1.EtcdDriver {
|
||||
if isValid, _ := etcd.IsETCDCertificateAndKeyPairValid(r.resource.Data["server.crt"], r.resource.Data["server.key"]); isValid {
|
||||
if isValid, _ := crypto.IsValidCertificateKeyPairBytes(r.resource.Data["server.crt"], r.resource.Data["server.key"]); isValid {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -99,11 +105,15 @@ func (r *Certificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1al
|
||||
// certificate used for authentication is mandatory, along with the CA private key.
|
||||
privateKey, err := r.DataStore.Spec.TLSConfig.CertificateAuthority.PrivateKey.GetContent(ctx, r.Client)
|
||||
if err != nil {
|
||||
logger.Error(err, "unable to retrieve CA private key content")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
crt, key, err = etcd.GetETCDCACertificateAndKeyPair(tenantControlPlane.GetName(), ca, privateKey)
|
||||
crt, key, err = crypto.GenerateCertificatePrivateKeyPair(crypto.NewCertificateTemplate(tenantControlPlane.GetName()), ca, privateKey)
|
||||
if err != nil {
|
||||
logger.Error(err, "unable to generate certificate and private key")
|
||||
|
||||
return err
|
||||
}
|
||||
case kamajiv1alpha1.KineMySQLDriver, kamajiv1alpha1.KinePostgreSQLDriver:
|
||||
@@ -111,12 +121,16 @@ func (r *Certificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1al
|
||||
// to connect to the desired schema and database.
|
||||
crtBytes, err := r.DataStore.Spec.TLSConfig.ClientCertificate.Certificate.GetContent(ctx, r.Client)
|
||||
if err != nil {
|
||||
logger.Error(err, "unable to retrieve certificate content")
|
||||
|
||||
return err
|
||||
}
|
||||
crt = bytes.NewBuffer(crtBytes)
|
||||
|
||||
keyBytes, err := r.DataStore.Spec.TLSConfig.ClientCertificate.PrivateKey.GetContent(ctx, r.Client)
|
||||
if err != nil {
|
||||
logger.Error(err, "unable to retrieve private key content")
|
||||
|
||||
return err
|
||||
}
|
||||
key = bytes.NewBuffer(keyBytes)
|
||||
@@ -131,7 +145,7 @@ func (r *Certificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1al
|
||||
if annotations == nil {
|
||||
annotations = map[string]string{}
|
||||
}
|
||||
annotations["checksum"] = utilities.CalculateConfigMapChecksum(r.resource.StringData)
|
||||
annotations[constants.Checksum] = utilities.CalculateMapChecksum(r.resource.Data)
|
||||
r.resource.SetAnnotations(annotations)
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
|
||||
@@ -6,11 +6,13 @@ package datastore
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/datastore"
|
||||
@@ -44,12 +46,16 @@ func (r *Setup) CleanUp(context.Context, *kamajiv1alpha1.TenantControlPlane) (bo
|
||||
}
|
||||
|
||||
func (r *Setup) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
secret := &corev1.Secret{}
|
||||
namespacedName := types.NamespacedName{
|
||||
Namespace: tenantControlPlane.GetNamespace(),
|
||||
Name: tenantControlPlane.Status.Storage.Config.SecretName,
|
||||
}
|
||||
if err := r.Client.Get(ctx, namespacedName, secret); err != nil {
|
||||
logger.Error(err, "cannot retrieve the DataStore Configuration secret")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -67,6 +73,8 @@ func (r *Setup) GetClient() client.Client {
|
||||
}
|
||||
|
||||
func (r *Setup) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
if tenantControlPlane.Status.Storage.Setup.Checksum != "" &&
|
||||
tenantControlPlane.Status.Storage.Setup.Checksum != tenantControlPlane.Status.Storage.Config.Checksum {
|
||||
if err := r.Delete(ctx, tenantControlPlane); err != nil {
|
||||
@@ -82,18 +90,24 @@ func (r *Setup) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1
|
||||
|
||||
operationResult, err = r.createDB(ctx, tenantControlPlane)
|
||||
if err != nil {
|
||||
logger.Error(err, "unable to create the DataStore data")
|
||||
|
||||
return reconcilationResult, err
|
||||
}
|
||||
reconcilationResult = utils.UpdateOperationResult(reconcilationResult, operationResult)
|
||||
|
||||
operationResult, err = r.createUser(ctx, tenantControlPlane)
|
||||
if err != nil {
|
||||
logger.Error(err, "unable to create the DataStore user")
|
||||
|
||||
return reconcilationResult, err
|
||||
}
|
||||
reconcilationResult = utils.UpdateOperationResult(reconcilationResult, operationResult)
|
||||
|
||||
operationResult, err = r.createGrantPrivileges(ctx, tenantControlPlane)
|
||||
if err != nil {
|
||||
logger.Error(err, "unable to create the DataStore user privileges")
|
||||
|
||||
return reconcilationResult, err
|
||||
}
|
||||
reconcilationResult = utils.UpdateOperationResult(reconcilationResult, operationResult)
|
||||
@@ -106,19 +120,23 @@ func (r *Setup) GetName() string {
|
||||
}
|
||||
|
||||
func (r *Setup) Delete(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
if err := r.Define(ctx, tenantControlPlane); err != nil {
|
||||
return err
|
||||
}
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
if err := r.revokeGrantPrivileges(ctx, tenantControlPlane); err != nil {
|
||||
logger.Error(err, "unable to revoke privileges")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.deleteDB(ctx, tenantControlPlane); err != nil {
|
||||
logger.Error(err, "unable to delete datastore data")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.deleteUser(ctx, tenantControlPlane); err != nil {
|
||||
logger.Error(err, "unable to delete user")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -137,7 +155,7 @@ func (r *Setup) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlP
|
||||
func (r *Setup) createDB(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
exists, err := r.Connection.DBExists(ctx, r.resource.schema)
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
return controllerutil.OperationResultNone, errors.Wrap(err, "unable to check if datastore exists")
|
||||
}
|
||||
|
||||
if exists {
|
||||
@@ -145,7 +163,7 @@ func (r *Setup) createDB(ctx context.Context, _ *kamajiv1alpha1.TenantControlPla
|
||||
}
|
||||
|
||||
if err := r.Connection.CreateDB(ctx, r.resource.schema); err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
return controllerutil.OperationResultNone, errors.Wrap(err, "unable to create the datastore")
|
||||
}
|
||||
|
||||
return controllerutil.OperationResultCreated, nil
|
||||
@@ -154,7 +172,7 @@ func (r *Setup) createDB(ctx context.Context, _ *kamajiv1alpha1.TenantControlPla
|
||||
func (r *Setup) deleteDB(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) error {
|
||||
exists, err := r.Connection.DBExists(ctx, r.resource.schema)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "unable to check if datastore exists")
|
||||
}
|
||||
|
||||
if !exists {
|
||||
@@ -162,7 +180,7 @@ func (r *Setup) deleteDB(ctx context.Context, _ *kamajiv1alpha1.TenantControlPla
|
||||
}
|
||||
|
||||
if err := r.Connection.DeleteDB(ctx, r.resource.schema); err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "unable to delete the datastore")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -171,7 +189,7 @@ func (r *Setup) deleteDB(ctx context.Context, _ *kamajiv1alpha1.TenantControlPla
|
||||
func (r *Setup) createUser(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
exists, err := r.Connection.UserExists(ctx, r.resource.user)
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
return controllerutil.OperationResultNone, errors.Wrap(err, "unable to check if user exists")
|
||||
}
|
||||
|
||||
if exists {
|
||||
@@ -179,7 +197,7 @@ func (r *Setup) createUser(ctx context.Context, _ *kamajiv1alpha1.TenantControlP
|
||||
}
|
||||
|
||||
if err := r.Connection.CreateUser(ctx, r.resource.user, r.resource.password); err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
return controllerutil.OperationResultNone, errors.Wrap(err, "unable to create the user")
|
||||
}
|
||||
|
||||
return controllerutil.OperationResultCreated, nil
|
||||
@@ -188,7 +206,7 @@ func (r *Setup) createUser(ctx context.Context, _ *kamajiv1alpha1.TenantControlP
|
||||
func (r *Setup) deleteUser(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) error {
|
||||
exists, err := r.Connection.UserExists(ctx, r.resource.user)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "unable to check if user exists")
|
||||
}
|
||||
|
||||
if !exists {
|
||||
@@ -196,7 +214,7 @@ func (r *Setup) deleteUser(ctx context.Context, _ *kamajiv1alpha1.TenantControlP
|
||||
}
|
||||
|
||||
if err := r.Connection.DeleteUser(ctx, r.resource.user); err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "unable to remove the user")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -205,7 +223,7 @@ func (r *Setup) deleteUser(ctx context.Context, _ *kamajiv1alpha1.TenantControlP
|
||||
func (r *Setup) createGrantPrivileges(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
exists, err := r.Connection.GrantPrivilegesExists(ctx, r.resource.user, r.resource.schema)
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
return controllerutil.OperationResultNone, errors.Wrap(err, "unable to check if privileges exist")
|
||||
}
|
||||
|
||||
if exists {
|
||||
@@ -213,7 +231,7 @@ func (r *Setup) createGrantPrivileges(ctx context.Context, _ *kamajiv1alpha1.Ten
|
||||
}
|
||||
|
||||
if err := r.Connection.GrantPrivileges(ctx, r.resource.user, r.resource.schema); err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
return controllerutil.OperationResultNone, errors.Wrap(err, "unable to grant privileges")
|
||||
}
|
||||
|
||||
return controllerutil.OperationResultCreated, nil
|
||||
@@ -222,7 +240,7 @@ func (r *Setup) createGrantPrivileges(ctx context.Context, _ *kamajiv1alpha1.Ten
|
||||
func (r *Setup) revokeGrantPrivileges(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) error {
|
||||
exists, err := r.Connection.GrantPrivilegesExists(ctx, r.resource.user, r.resource.schema)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "unable to check if privileges exist")
|
||||
}
|
||||
|
||||
if !exists {
|
||||
@@ -230,7 +248,7 @@ func (r *Setup) revokeGrantPrivileges(ctx context.Context, _ *kamajiv1alpha1.Ten
|
||||
}
|
||||
|
||||
if err := r.Connection.RevokePrivileges(ctx, r.resource.user, r.resource.schema); err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "unable to revoke privileges")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -6,6 +6,7 @@ package datastore
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/constants"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
@@ -20,12 +22,12 @@ type Config struct {
|
||||
resource *corev1.Secret
|
||||
Client client.Client
|
||||
ConnString string
|
||||
Driver string
|
||||
DataStore kamajiv1alpha1.DataStore
|
||||
}
|
||||
|
||||
func (r *Config) ShouldStatusBeUpdated(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return tenantControlPlane.Status.Storage.Config.Checksum != r.resource.GetAnnotations()["checksum"] ||
|
||||
tenantControlPlane.Status.Storage.Driver != r.Driver
|
||||
return tenantControlPlane.Status.Storage.Config.Checksum != r.resource.GetAnnotations()[constants.Checksum] ||
|
||||
tenantControlPlane.Status.Storage.DataStoreName != r.DataStore.GetName()
|
||||
}
|
||||
|
||||
func (r *Config) ShouldCleanup(*kamajiv1alpha1.TenantControlPlane) bool {
|
||||
@@ -64,9 +66,10 @@ func (r *Config) GetName() string {
|
||||
}
|
||||
|
||||
func (r *Config) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
tenantControlPlane.Status.Storage.Driver = r.Driver
|
||||
tenantControlPlane.Status.Storage.Driver = string(r.DataStore.Spec.Driver)
|
||||
tenantControlPlane.Status.Storage.DataStoreName = r.DataStore.GetName()
|
||||
tenantControlPlane.Status.Storage.Config.SecretName = r.resource.GetName()
|
||||
tenantControlPlane.Status.Storage.Config.Checksum = r.resource.GetAnnotations()["checksum"]
|
||||
tenantControlPlane.Status.Storage.Config.Checksum = r.resource.GetAnnotations()[constants.Checksum]
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -75,12 +78,12 @@ func (r *Config) mutate(_ context.Context, tenantControlPlane *kamajiv1alpha1.Te
|
||||
return func() error {
|
||||
var password []byte
|
||||
|
||||
savedHash, ok := r.resource.GetAnnotations()["checksum"]
|
||||
savedHash, ok := r.resource.GetAnnotations()[constants.Checksum]
|
||||
switch {
|
||||
case ok && savedHash == utilities.CalculateConfigMapChecksum(r.resource.StringData):
|
||||
case ok && savedHash == utilities.CalculateMapChecksum(r.resource.Data):
|
||||
password = r.resource.Data["DB_PASSWORD"]
|
||||
default:
|
||||
password = []byte(utilities.GenerateUUIDString())
|
||||
password = []byte(uuid.New().String())
|
||||
}
|
||||
|
||||
r.resource.Data = map[string][]byte{
|
||||
@@ -95,7 +98,7 @@ func (r *Config) mutate(_ context.Context, tenantControlPlane *kamajiv1alpha1.Te
|
||||
annotations = map[string]string{}
|
||||
}
|
||||
|
||||
annotations["checksum"] = utilities.CalculateConfigMapChecksum(r.resource.StringData)
|
||||
annotations[constants.Checksum] = utilities.CalculateMapChecksum(r.resource.Data)
|
||||
r.resource.SetAnnotations(annotations)
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
@@ -15,8 +14,11 @@ import (
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/constants"
|
||||
"github.com/clastix/kamaji/internal/crypto"
|
||||
"github.com/clastix/kamaji/internal/kubeadm"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
@@ -24,12 +26,11 @@ import (
|
||||
type FrontProxyClientCertificate struct {
|
||||
resource *corev1.Secret
|
||||
Client client.Client
|
||||
Log logr.Logger
|
||||
TmpDirectory string
|
||||
}
|
||||
|
||||
func (r *FrontProxyClientCertificate) ShouldStatusBeUpdated(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return tenantControlPlane.Status.Certificates.FrontProxyClient.Checksum != r.resource.GetAnnotations()["checksum"]
|
||||
return tenantControlPlane.Status.Certificates.FrontProxyClient.Checksum != r.resource.GetAnnotations()[constants.Checksum]
|
||||
}
|
||||
|
||||
func (r *FrontProxyClientCertificate) ShouldCleanup(*kamajiv1alpha1.TenantControlPlane) bool {
|
||||
@@ -74,20 +75,22 @@ func (r *FrontProxyClientCertificate) GetName() string {
|
||||
func (r *FrontProxyClientCertificate) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
tenantControlPlane.Status.Certificates.FrontProxyClient.LastUpdate = metav1.Now()
|
||||
tenantControlPlane.Status.Certificates.FrontProxyClient.SecretName = r.resource.GetName()
|
||||
tenantControlPlane.Status.Certificates.FrontProxyClient.Checksum = r.resource.GetAnnotations()["checksum"]
|
||||
tenantControlPlane.Status.Certificates.FrontProxyClient.Checksum = r.resource.GetAnnotations()[constants.Checksum]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *FrontProxyClientCertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
|
||||
return func() error {
|
||||
if checksum := tenantControlPlane.Status.Certificates.FrontProxyClient.Checksum; len(checksum) > 0 && checksum == r.resource.GetAnnotations()["checksum"] {
|
||||
isValid, err := kubeadm.IsCertificatePrivateKeyPairValid(
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
if checksum := tenantControlPlane.Status.Certificates.FrontProxyClient.Checksum; len(checksum) > 0 && checksum == r.resource.GetAnnotations()[constants.Checksum] {
|
||||
isValid, err := crypto.CheckCertificateAndPrivateKeyPairValidity(
|
||||
r.resource.Data[kubeadmconstants.FrontProxyClientCertName],
|
||||
r.resource.Data[kubeadmconstants.FrontProxyClientKeyName],
|
||||
)
|
||||
if err != nil {
|
||||
r.Log.Info(fmt.Sprintf("%s certificate-private_key pair is not valid: %s", kubeadmconstants.FrontProxyClientCertAndKeyBaseName, err.Error()))
|
||||
logger.Info(fmt.Sprintf("%s certificate-private_key pair is not valid: %s", kubeadmconstants.FrontProxyClientCertAndKeyBaseName, err.Error()))
|
||||
}
|
||||
if isValid {
|
||||
return nil
|
||||
@@ -96,12 +99,16 @@ func (r *FrontProxyClientCertificate) mutate(ctx context.Context, tenantControlP
|
||||
|
||||
config, err := getStoredKubeadmConfiguration(ctx, r, tenantControlPlane)
|
||||
if err != nil {
|
||||
logger.Error(err, "cannot retrieve kubeadm configuration")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
namespacedName := k8stypes.NamespacedName{Namespace: tenantControlPlane.GetNamespace(), Name: tenantControlPlane.Status.Certificates.FrontProxyCA.SecretName}
|
||||
secretCA := &corev1.Secret{}
|
||||
if err = r.Client.Get(ctx, namespacedName, secretCA); err != nil {
|
||||
logger.Error(err, "cannot retrieve CA secret")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -112,6 +119,8 @@ func (r *FrontProxyClientCertificate) mutate(ctx context.Context, tenantControlP
|
||||
}
|
||||
certificateKeyPair, err := kubeadm.GenerateCertificatePrivateKeyPair(kubeadmconstants.FrontProxyClientCertAndKeyBaseName, config, ca)
|
||||
if err != nil {
|
||||
logger.Error(err, "cannot generate certificate and private key")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -132,7 +141,7 @@ func (r *FrontProxyClientCertificate) mutate(ctx context.Context, tenantControlP
|
||||
if annotations == nil {
|
||||
annotations = map[string]string{}
|
||||
}
|
||||
annotations["checksum"] = utilities.CalculateConfigMapChecksum(r.resource.StringData)
|
||||
annotations[constants.Checksum] = utilities.CalculateMapChecksum(r.resource.Data)
|
||||
r.resource.SetAnnotations(annotations)
|
||||
|
||||
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
|
||||
|
||||
@@ -7,15 +7,17 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/constants"
|
||||
"github.com/clastix/kamaji/internal/crypto"
|
||||
"github.com/clastix/kamaji/internal/kubeadm"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
@@ -23,12 +25,11 @@ import (
|
||||
type FrontProxyCACertificate struct {
|
||||
resource *corev1.Secret
|
||||
Client client.Client
|
||||
Log logr.Logger
|
||||
TmpDirectory string
|
||||
}
|
||||
|
||||
func (r *FrontProxyCACertificate) ShouldStatusBeUpdated(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return tenantControlPlane.Status.Certificates.FrontProxyCA.Checksum != r.resource.GetAnnotations()["checksum"]
|
||||
return tenantControlPlane.Status.Certificates.FrontProxyCA.Checksum != r.resource.GetAnnotations()[constants.Checksum]
|
||||
}
|
||||
|
||||
func (r *FrontProxyCACertificate) ShouldCleanup(*kamajiv1alpha1.TenantControlPlane) bool {
|
||||
@@ -73,20 +74,22 @@ func (r *FrontProxyCACertificate) GetName() string {
|
||||
func (r *FrontProxyCACertificate) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
tenantControlPlane.Status.Certificates.FrontProxyCA.LastUpdate = metav1.Now()
|
||||
tenantControlPlane.Status.Certificates.FrontProxyCA.SecretName = r.resource.GetName()
|
||||
tenantControlPlane.Status.Certificates.FrontProxyCA.Checksum = r.resource.GetAnnotations()["checksum"]
|
||||
tenantControlPlane.Status.Certificates.FrontProxyCA.Checksum = r.resource.GetAnnotations()[constants.Checksum]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *FrontProxyCACertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
|
||||
return func() error {
|
||||
if checksum := tenantControlPlane.Status.Certificates.FrontProxyCA.Checksum; len(checksum) > 0 && checksum == r.resource.GetAnnotations()["checksum"] {
|
||||
isValid, err := kubeadm.IsCertificatePrivateKeyPairValid(
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
if checksum := tenantControlPlane.Status.Certificates.FrontProxyCA.Checksum; len(checksum) > 0 && checksum == r.resource.GetAnnotations()[constants.Checksum] {
|
||||
isValid, err := crypto.CheckCertificateAndPrivateKeyPairValidity(
|
||||
r.resource.Data[kubeadmconstants.FrontProxyCACertName],
|
||||
r.resource.Data[kubeadmconstants.FrontProxyCAKeyName],
|
||||
)
|
||||
if err != nil {
|
||||
r.Log.Info(fmt.Sprintf("%s certificate-private_key pair is not valid: %s", kubeadmconstants.FrontProxyCACertAndKeyBaseName, err.Error()))
|
||||
logger.Info(fmt.Sprintf("%s certificate-private_key pair is not valid: %s", kubeadmconstants.FrontProxyCACertAndKeyBaseName, err.Error()))
|
||||
}
|
||||
if isValid {
|
||||
return nil
|
||||
@@ -95,11 +98,15 @@ func (r *FrontProxyCACertificate) mutate(ctx context.Context, tenantControlPlane
|
||||
|
||||
config, err := getStoredKubeadmConfiguration(ctx, r, tenantControlPlane)
|
||||
if err != nil {
|
||||
logger.Error(err, "cannot retrieve kubeadm configuration")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
ca, err := kubeadm.GenerateCACertificatePrivateKeyPair(kubeadmconstants.FrontProxyCACertAndKeyBaseName, config)
|
||||
if err != nil {
|
||||
logger.Error(err, "cannot generate certificate and private key")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -120,7 +127,7 @@ func (r *FrontProxyCACertificate) mutate(ctx context.Context, tenantControlPlane
|
||||
if annotations == nil {
|
||||
annotations = map[string]string{}
|
||||
}
|
||||
annotations["checksum"] = utilities.CalculateConfigMapChecksum(r.resource.StringData)
|
||||
annotations[constants.Checksum] = utilities.CalculateMapChecksum(r.resource.Data)
|
||||
r.resource.SetAnnotations(annotations)
|
||||
|
||||
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
|
||||
|
||||
@@ -5,12 +5,18 @@ package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
builder "github.com/clastix/kamaji/internal/builders/controlplane"
|
||||
@@ -56,9 +62,13 @@ func (r *KubernetesDeploymentResource) Define(_ context.Context, tenantControlPl
|
||||
|
||||
func (r *KubernetesDeploymentResource) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
|
||||
return func() error {
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
address, _, err := tenantControlPlane.AssignedControlPlaneAddress()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot create TenantControlPlane Deployment")
|
||||
logger.Error(err, "cannot retrieve Tenant Control Plane address")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
d := builder.Deployment{
|
||||
@@ -69,8 +79,12 @@ func (r *KubernetesDeploymentResource) mutate(ctx context.Context, tenantControl
|
||||
d.SetLabels(r.resource, utilities.MergeMaps(utilities.CommonLabels(tenantControlPlane.GetName()), tenantControlPlane.Spec.ControlPlane.Deployment.AdditionalMetadata.Labels))
|
||||
d.SetAnnotations(r.resource, utilities.MergeMaps(r.resource.Annotations, tenantControlPlane.Spec.ControlPlane.Deployment.AdditionalMetadata.Annotations))
|
||||
d.SetTemplateLabels(&r.resource.Spec.Template, r.deploymentTemplateLabels(ctx, tenantControlPlane))
|
||||
d.SetNodeSelector(&r.resource.Spec.Template.Spec, tenantControlPlane)
|
||||
d.SetToleration(&r.resource.Spec.Template.Spec, tenantControlPlane)
|
||||
d.SetAffinity(&r.resource.Spec.Template.Spec, tenantControlPlane)
|
||||
d.SetStrategy(&r.resource.Spec)
|
||||
d.SetSelector(&r.resource.Spec, tenantControlPlane)
|
||||
d.SetTopologySpreadConstraints(&r.resource.Spec, tenantControlPlane.Spec.ControlPlane.Deployment.TopologySpreadConstraints)
|
||||
d.SetReplicas(&r.resource.Spec, tenantControlPlane)
|
||||
d.ResetKubeAPIServerFlags(r.resource, tenantControlPlane)
|
||||
d.SetContainers(&r.resource.Spec.Template.Spec, tenantControlPlane, address)
|
||||
@@ -103,6 +117,7 @@ func (r *KubernetesDeploymentResource) UpdateTenantControlPlaneStatus(_ context.
|
||||
|
||||
tenantControlPlane.Status.Kubernetes.Deployment = kamajiv1alpha1.KubernetesDeploymentStatus{
|
||||
DeploymentStatus: r.resource.Status,
|
||||
Selector: metav1.FormatLabelSelector(r.resource.Spec.Selector),
|
||||
Name: r.resource.GetName(),
|
||||
Namespace: r.resource.GetNamespace(),
|
||||
LastUpdate: metav1.Now(),
|
||||
@@ -113,7 +128,7 @@ func (r *KubernetesDeploymentResource) UpdateTenantControlPlaneStatus(_ context.
|
||||
|
||||
func (r *KubernetesDeploymentResource) deploymentTemplateLabels(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (labels map[string]string) {
|
||||
hash := func(ctx context.Context, namespace, secretName string) string {
|
||||
h, _ := utilities.SecretHashValue(ctx, r.Client, namespace, secretName)
|
||||
h, _ := r.SecretHashValue(ctx, r.Client, namespace, secretName)
|
||||
|
||||
return h
|
||||
}
|
||||
@@ -158,3 +173,33 @@ func (r *KubernetesDeploymentResource) isProvisioning(tenantControlPlane *kamaji
|
||||
func (r *KubernetesDeploymentResource) isNotReady() bool {
|
||||
return r.resource.Status.ReadyReplicas == 0
|
||||
}
|
||||
|
||||
// SecretHashValue function returns the md5 value for the secret of the given name and namespace.
|
||||
func (r *KubernetesDeploymentResource) SecretHashValue(ctx context.Context, client client.Client, namespace, name string) (string, error) {
|
||||
secret := &corev1.Secret{}
|
||||
if err := client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, secret); err != nil {
|
||||
return "", errors.Wrap(err, "cannot retrieve *corev1.Secret for resource version retrieval")
|
||||
}
|
||||
|
||||
return r.HashValue(*secret), nil
|
||||
}
|
||||
|
||||
// HashValue function returns the md5 value for the given secret.
|
||||
func (r *KubernetesDeploymentResource) HashValue(secret corev1.Secret) string {
|
||||
// Go access map values in random way, it means we have to sort them.
|
||||
keys := make([]string, 0, len(secret.Data))
|
||||
|
||||
for k := range secret.Data {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
// Generating MD5 of Secret values, sorted by key
|
||||
h := md5.New()
|
||||
|
||||
for _, key := range keys {
|
||||
h.Write(secret.Data[key])
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
@@ -34,8 +35,12 @@ func (r *KubernetesIngressResource) ShouldCleanup(tenantControlPlane *kamajiv1al
|
||||
}
|
||||
|
||||
func (r *KubernetesIngressResource) CleanUp(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) (bool, error) {
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
if err := r.Client.Delete(ctx, r.resource); err != nil {
|
||||
if !k8serrors.IsNotFound(err) {
|
||||
logger.Error(err, "cannot cleanup resource")
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
@@ -39,6 +40,8 @@ func (r *KubernetesServiceResource) CleanUp(context.Context, *kamajiv1alpha1.Ten
|
||||
}
|
||||
|
||||
func (r *KubernetesServiceResource) UpdateTenantControlPlaneStatus(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
tenantControlPlane.Status.Kubernetes.Service.ServiceStatus = r.resource.Status
|
||||
tenantControlPlane.Status.Kubernetes.Service.Name = r.resource.GetName()
|
||||
tenantControlPlane.Status.Kubernetes.Service.Namespace = r.resource.GetNamespace()
|
||||
@@ -46,6 +49,8 @@ func (r *KubernetesServiceResource) UpdateTenantControlPlaneStatus(ctx context.C
|
||||
|
||||
address, err := tenantControlPlane.DeclaredControlPlaneAddress(ctx, r.Client)
|
||||
if err != nil {
|
||||
logger.Error(err, "cannot retrieve Tenant Control Plane address")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user