Compare commits
39 Commits
helm-v0.1.
...
v0.1.1-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c7804e1bf | ||
|
|
c4481f26f7 | ||
|
|
ec715d2e8f | ||
|
|
0aeaf89cb7 | ||
|
|
3d31ddb4e3 | ||
|
|
e83f344cdc | ||
|
|
da83a8711a | ||
|
|
43a944ace0 | ||
|
|
0acc2d2ef1 | ||
|
|
14f9686bbb | ||
|
|
6ba9826c51 | ||
|
|
bd58084ded | ||
|
|
3a5e50886d | ||
|
|
e2768dad83 | ||
|
|
b97c23176d | ||
|
|
fa8e805842 | ||
|
|
8df66fc232 | ||
|
|
c2218912eb | ||
|
|
e361e2d424 | ||
|
|
260b60d263 | ||
|
|
e0d5e6feb2 | ||
|
|
0784dc7177 | ||
|
|
b17c6c4636 | ||
|
|
52cf597041 | ||
|
|
b8dcded882 | ||
|
|
6a175e9017 | ||
|
|
3c609f84db | ||
|
|
7c3a59c4e4 | ||
|
|
d3e3b8a881 | ||
|
|
7a8148bd58 | ||
|
|
405d3ac52d | ||
|
|
f92acf9a9d | ||
|
|
bbb7b850d6 | ||
|
|
0f7284d190 | ||
|
|
7db263b2b6 | ||
|
|
0a8f50f761 | ||
|
|
7a66e8ea93 | ||
|
|
b5eb03ea76 | ||
|
|
681b514516 |
18
.github/workflows/e2e.yml
vendored
@@ -3,8 +3,26 @@ name: e2e
|
||||
on:
|
||||
push:
|
||||
branches: [ "*" ]
|
||||
paths:
|
||||
- '.github/workflows/e2e.yml'
|
||||
- 'api/**'
|
||||
- 'controllers/**'
|
||||
- 'e2e/*'
|
||||
- 'Dockerfile'
|
||||
- 'go.*'
|
||||
- 'main.go'
|
||||
- 'Makefile'
|
||||
pull_request:
|
||||
branches: [ "*" ]
|
||||
paths:
|
||||
- '.github/workflows/e2e.yml'
|
||||
- 'api/**'
|
||||
- 'controllers/**'
|
||||
- 'e2e/*'
|
||||
- 'Dockerfile'
|
||||
- 'go.*'
|
||||
- 'main.go'
|
||||
- 'Makefile'
|
||||
|
||||
jobs:
|
||||
kind:
|
||||
|
||||
3
.github/workflows/helm.yml
vendored
@@ -3,10 +3,9 @@ name: Helm Chart
|
||||
on:
|
||||
push:
|
||||
branches: [ "*" ]
|
||||
tags: [ "helm-v*" ]
|
||||
pull_request:
|
||||
branches: [ "*" ]
|
||||
create:
|
||||
branches: [ "*" ]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
|
||||
1
.gitignore
vendored
@@ -22,6 +22,7 @@ bin
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.vscode
|
||||
|
||||
**/*.kubeconfig
|
||||
**/*.crt
|
||||
|
||||
54
Makefile
@@ -45,7 +45,7 @@ manager: generate fmt vet
|
||||
|
||||
# Run against the configured Kubernetes cluster in ~/.kube/config
|
||||
run: generate manifests
|
||||
go run ./main.go
|
||||
go run .
|
||||
|
||||
# Creates the single file to install Capsule without any external dependency
|
||||
installer: manifests kustomize
|
||||
@@ -78,6 +78,58 @@ manifests: controller-gen
|
||||
generate: controller-gen
|
||||
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."
|
||||
|
||||
# Setup development env
|
||||
# Usage:
|
||||
# LAPTOP_HOST_IP=<YOUR_LAPTOP_IP> make dev-setup
|
||||
# For example:
|
||||
# LAPTOP_HOST_IP=192.168.10.101 make dev-setup
|
||||
define TLS_CNF
|
||||
[ req ]
|
||||
default_bits = 4096
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = req_ext
|
||||
[ req_distinguished_name ]
|
||||
countryName = SG
|
||||
stateOrProvinceName = SG
|
||||
localityName = SG
|
||||
organizationName = CAPSULE
|
||||
commonName = CAPSULE
|
||||
[ req_ext ]
|
||||
subjectAltName = @alt_names
|
||||
[alt_names]
|
||||
IP.1 = $(LAPTOP_HOST_IP)
|
||||
endef
|
||||
export TLS_CNF
|
||||
dev-setup:
|
||||
kubectl -n capsule-system scale deployment capsule-controller-manager --replicas=0
|
||||
mkdir -p /tmp/k8s-webhook-server/serving-certs
|
||||
echo "$${TLS_CNF}" > _tls.cnf
|
||||
openssl req -newkey rsa:4096 -days 3650 -nodes -x509 \
|
||||
-subj "/C=SG/ST=SG/L=SG/O=CAPSULE/CN=CAPSULE" \
|
||||
-extensions req_ext \
|
||||
-config _tls.cnf \
|
||||
-keyout /tmp/k8s-webhook-server/serving-certs/tls.key \
|
||||
-out /tmp/k8s-webhook-server/serving-certs/tls.crt
|
||||
rm -f _tls.cnf
|
||||
export WEBHOOK_URL="https://$${LAPTOP_HOST_IP}:9443"; \
|
||||
export CA_BUNDLE=`openssl base64 -in /tmp/k8s-webhook-server/serving-certs/tls.crt | tr -d '\n'`; \
|
||||
kubectl patch MutatingWebhookConfiguration capsule-mutating-webhook-configuration \
|
||||
--type='json' -p="[\
|
||||
{'op': 'replace', 'path': '/webhooks/0/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/mutate-v1-namespace-owner-reference\",'caBundle':\"$${CA_BUNDLE}\"}}\
|
||||
]" && \
|
||||
kubectl patch ValidatingWebhookConfiguration capsule-validating-webhook-configuration \
|
||||
--type='json' -p="[\
|
||||
{'op': 'replace', 'path': '/webhooks/0/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/cordoning\",'caBundle':\"$${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/1/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/ingresses\",'caBundle':\"$${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/2/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/namespaces\",'caBundle':\"$${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/3/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/networkpolicies\",'caBundle':\"$${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/4/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/pods\",'caBundle':\"$${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/5/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/persistentvolumeclaims\",'caBundle':\"$${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/6/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/services\",'caBundle':\"$${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/7/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/tenants\",'caBundle':\"$${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/8/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/nodes\",'caBundle':\"$${CA_BUNDLE}\"}}\
|
||||
]";
|
||||
|
||||
# Build the docker image
|
||||
docker-build: test
|
||||
docker build . -t ${IMG} --build-arg GIT_HEAD_COMMIT=$(GIT_HEAD_COMMIT) \
|
||||
|
||||
17
README.md
@@ -150,9 +150,6 @@ Error from server (Forbidden): pods is forbidden:
|
||||
User "bob" cannot list resource "pods" in API group "" in the namespace "kube-system"
|
||||
```
|
||||
|
||||
# Documentation
|
||||
Please, check the project [documentation](./docs/index.md) for more cool things you can do with Capsule.
|
||||
|
||||
# Removal
|
||||
Similar to `deploy`, you can get rid of Capsule using the `remove` target.
|
||||
|
||||
@@ -160,15 +157,21 @@ Similar to `deploy`, you can get rid of Capsule using the `remove` target.
|
||||
$ make remove
|
||||
```
|
||||
|
||||
# Documentation
|
||||
Please, check the project [documentation](./docs/index.md) for more cool things you can do with Capsule.
|
||||
|
||||
# Contribution
|
||||
Capsule is Open Source with Apache 2 license and any contribution is welcome.
|
||||
|
||||
Please refer to the corresponding docs:
|
||||
- [contributing.md](./docs/contributing.md) for the general guide; and
|
||||
- [dev-guide.md](./docs/dev-guide.md) for how to set up the development env to get started.
|
||||
|
||||
# FAQ
|
||||
- Q. How to pronounce Capsule?
|
||||
|
||||
A. It should be pronounced as `/ˈkæpsjuːl/`.
|
||||
|
||||
- Q. Can I contribute?
|
||||
|
||||
A. Absolutely! Capsule is Open Source with Apache 2 license and any contribution is welcome. Please refer to the corresponding [section](./docs/operator/contributing.md) in the documentation.
|
||||
|
||||
- Q. Is it production grade?
|
||||
|
||||
A. Although under frequent development and improvements, Capsule is ready to be used in production environments as currently, people are using it in public and private deployments. Check out the [release](https://github.com/clastix/capsule/releases) page for a detailed list of available versions.
|
||||
|
||||
8
api/v1alpha1/capsuleconfiguration_annotations.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package v1alpha1
|
||||
|
||||
const (
|
||||
ForbiddenNodeLabelsAnnotation = "capsule.clastix.io/forbidden-node-labels"
|
||||
ForbiddenNodeLabelsRegexpAnnotation = "capsule.clastix.io/forbidden-node-labels-regexp"
|
||||
ForbiddenNodeAnnotationsAnnotation = "capsule.clastix.io/forbidden-node-annotations"
|
||||
ForbiddenNodeAnnotationsRegexpAnnotation = "capsule.clastix.io/forbidden-node-annotations-regexp"
|
||||
)
|
||||
@@ -200,17 +200,17 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error {
|
||||
}
|
||||
}
|
||||
if len(t.Spec.NetworkPolicies) > 0 {
|
||||
dst.Spec.NetworkPolicies = &capsulev1beta1.NetworkPolicySpec{
|
||||
dst.Spec.NetworkPolicies = capsulev1beta1.NetworkPolicySpec{
|
||||
Items: t.Spec.NetworkPolicies,
|
||||
}
|
||||
}
|
||||
if len(t.Spec.LimitRanges) > 0 {
|
||||
dst.Spec.LimitRanges = &capsulev1beta1.LimitRangesSpec{
|
||||
dst.Spec.LimitRanges = capsulev1beta1.LimitRangesSpec{
|
||||
Items: t.Spec.LimitRanges,
|
||||
}
|
||||
}
|
||||
if len(t.Spec.ResourceQuota) > 0 {
|
||||
dst.Spec.ResourceQuota = &capsulev1beta1.ResourceQuotaSpec{
|
||||
dst.Spec.ResourceQuota = capsulev1beta1.ResourceQuotaSpec{
|
||||
Scope: func() capsulev1beta1.ResourceQuotaScope {
|
||||
if v, ok := t.GetAnnotations()[resourceQuotaScopeAnnotation]; ok {
|
||||
switch v {
|
||||
@@ -500,13 +500,13 @@ func (t *Tenant) ConvertFrom(srcRaw conversion.Hub) error {
|
||||
Regex: src.Spec.ContainerRegistries.Regex,
|
||||
}
|
||||
}
|
||||
if src.Spec.NetworkPolicies != nil {
|
||||
if len(src.Spec.NetworkPolicies.Items) > 0 {
|
||||
t.Spec.NetworkPolicies = src.Spec.NetworkPolicies.Items
|
||||
}
|
||||
if src.Spec.LimitRanges != nil {
|
||||
if len(src.Spec.LimitRanges.Items) > 0 {
|
||||
t.Spec.LimitRanges = src.Spec.LimitRanges.Items
|
||||
}
|
||||
if src.Spec.ResourceQuota != nil {
|
||||
if len(src.Spec.ResourceQuota.Items) > 0 {
|
||||
t.Annotations[resourceQuotaScopeAnnotation] = string(src.Spec.ResourceQuota.Scope)
|
||||
t.Spec.ResourceQuota = src.Spec.ResourceQuota.Items
|
||||
}
|
||||
@@ -545,9 +545,15 @@ func (t *Tenant) ConvertFrom(srcRaw conversion.Hub) error {
|
||||
}
|
||||
|
||||
if src.Spec.ServiceOptions != nil && src.Spec.ServiceOptions.AllowedServices != nil {
|
||||
t.Annotations[enableNodePortsAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.NodePort)
|
||||
t.Annotations[enableExternalNameAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.ExternalName)
|
||||
t.Annotations[enableLoadBalancerAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.LoadBalancer)
|
||||
if src.Spec.ServiceOptions.AllowedServices.NodePort != nil {
|
||||
t.Annotations[enableNodePortsAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.NodePort)
|
||||
}
|
||||
if src.Spec.ServiceOptions.AllowedServices.ExternalName != nil {
|
||||
t.Annotations[enableExternalNameAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.ExternalName)
|
||||
}
|
||||
if src.Spec.ServiceOptions.AllowedServices.LoadBalancer != nil {
|
||||
t.Annotations[enableLoadBalancerAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.LoadBalancer)
|
||||
}
|
||||
}
|
||||
|
||||
// Status
|
||||
|
||||
@@ -240,13 +240,13 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
|
||||
},
|
||||
ContainerRegistries: v1beta1AllowedListSpec,
|
||||
NodeSelector: nodeSelector,
|
||||
NetworkPolicies: &capsulev1beta1.NetworkPolicySpec{
|
||||
NetworkPolicies: capsulev1beta1.NetworkPolicySpec{
|
||||
Items: networkPolicies,
|
||||
},
|
||||
LimitRanges: &capsulev1beta1.LimitRangesSpec{
|
||||
LimitRanges: capsulev1beta1.LimitRangesSpec{
|
||||
Items: limitRanges,
|
||||
},
|
||||
ResourceQuota: &capsulev1beta1.ResourceQuotaSpec{
|
||||
ResourceQuota: capsulev1beta1.ResourceQuotaSpec{
|
||||
Scope: capsulev1beta1.ResourceQuotaScopeNamespace,
|
||||
Items: resourceQuotas,
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
|
||||
@@ -24,11 +24,11 @@ type TenantSpec struct {
|
||||
// Specifies the label to control the placement of pods on a given pool of worker nodes. All namesapces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional.
|
||||
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
|
||||
// Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
|
||||
NetworkPolicies *NetworkPolicySpec `json:"networkPolicies,omitempty"`
|
||||
NetworkPolicies NetworkPolicySpec `json:"networkPolicies,omitempty"`
|
||||
// Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
|
||||
LimitRanges *LimitRangesSpec `json:"limitRanges,omitempty"`
|
||||
LimitRanges LimitRangesSpec `json:"limitRanges,omitempty"`
|
||||
// Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional.
|
||||
ResourceQuota *ResourceQuotaSpec `json:"resourceQuotas,omitempty"`
|
||||
ResourceQuota ResourceQuotaSpec `json:"resourceQuotas,omitempty"`
|
||||
// Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional.
|
||||
AdditionalRoleBindings []AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"`
|
||||
// Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
@@ -480,21 +481,9 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.NetworkPolicies != nil {
|
||||
in, out := &in.NetworkPolicies, &out.NetworkPolicies
|
||||
*out = new(NetworkPolicySpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.LimitRanges != nil {
|
||||
in, out := &in.LimitRanges, &out.LimitRanges
|
||||
*out = new(LimitRangesSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ResourceQuota != nil {
|
||||
in, out := &in.ResourceQuota, &out.ResourceQuota
|
||||
*out = new(ResourceQuotaSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
in.NetworkPolicies.DeepCopyInto(&out.NetworkPolicies)
|
||||
in.LimitRanges.DeepCopyInto(&out.LimitRanges)
|
||||
in.ResourceQuota.DeepCopyInto(&out.ResourceQuota)
|
||||
if in.AdditionalRoleBindings != nil {
|
||||
in, out := &in.AdditionalRoleBindings, &out.AdditionalRoleBindings
|
||||
*out = make([]AdditionalRoleBindingsSpec, len(*in))
|
||||
|
||||
@@ -21,7 +21,7 @@ sources:
|
||||
|
||||
# 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.
|
||||
version: 0.1.1
|
||||
version: 0.1.3
|
||||
|
||||
# This is the version number of the application being deployed.
|
||||
# This version number should be incremented each time you make changes to the application.
|
||||
|
||||
@@ -80,6 +80,7 @@ Parameter | Description | Default
|
||||
`manager.resources.limits/cpu` | Set the memory limits assigned to the controller. | `128Mi`
|
||||
`mutatingWebhooksTimeoutSeconds` | Timeout in seconds for mutating webhooks. | `30`
|
||||
`validatingWebhooksTimeoutSeconds` | Timeout in seconds for validating webhooks. | `30`
|
||||
`webhooks` | Additional configuration for capsule webhooks. |
|
||||
`imagePullSecrets` | Configuration for `imagePullSecrets` so that you can use a private images registry. | `[]`
|
||||
`serviceAccount.create` | Specifies whether a service account should be created. | `true`
|
||||
`serviceAccount.annotations` | Annotations to add to the service account. | `{}`
|
||||
|
||||
@@ -23,7 +23,7 @@ webhooks:
|
||||
matchPolicy: Equivalent
|
||||
name: cordoning.tenant.capsule.clastix.io
|
||||
namespaceSelector:
|
||||
{{- toYaml .Values.webhooks.cordoning.namespaceSelector | nindent 4}}
|
||||
{{- toYaml .Values.webhooks.cordoning.namespaceSelector | nindent 4}}
|
||||
objectSelector: {}
|
||||
rules:
|
||||
- apiGroups:
|
||||
@@ -53,10 +53,7 @@ webhooks:
|
||||
matchPolicy: Equivalent
|
||||
name: ingress.capsule.clastix.io
|
||||
namespaceSelector:
|
||||
{{- toYaml .Values.webhooks.ingresses.namespaceSelector | nindent 4}}
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
{{- toYaml .Values.webhooks.ingresses.namespaceSelector | nindent 4}}
|
||||
objectSelector: {}
|
||||
rules:
|
||||
- apiGroups:
|
||||
@@ -166,7 +163,7 @@ webhooks:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ include "capsule.fullname" . }}-webhook-service
|
||||
namespace: capsule-system
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /persistentvolumeclaims
|
||||
failurePolicy: {{ .Values.webhooks.persistentvolumeclaims.failurePolicy }}
|
||||
name: pvc.capsule.clastix.io
|
||||
@@ -243,3 +240,29 @@ webhooks:
|
||||
scope: '*'
|
||||
sideEffects: None
|
||||
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ include "capsule.fullname" . }}-webhook-service
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /nodes
|
||||
port: 443
|
||||
failurePolicy: {{ .Values.webhooks.nodes.failurePolicy }}
|
||||
name: nodes.capsule.clastix.io
|
||||
matchPolicy: Exact
|
||||
namespaceSelector: {}
|
||||
objectSelector: {}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- UPDATE
|
||||
resources:
|
||||
- nodes
|
||||
sideEffects: None
|
||||
timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }}
|
||||
|
||||
@@ -123,5 +123,7 @@ webhooks:
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
nodes:
|
||||
failurePolicy: Fail
|
||||
mutatingWebhooksTimeoutSeconds: 30
|
||||
validatingWebhooksTimeoutSeconds: 30
|
||||
|
||||
@@ -1411,7 +1411,7 @@ spec:
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
image: quay.io/clastix/capsule:v0.1.0
|
||||
image: quay.io/clastix/capsule:v0.1.1-rc0
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: manager
|
||||
ports:
|
||||
@@ -1588,14 +1588,33 @@ webhooks:
|
||||
service:
|
||||
name: capsule-webhook-service
|
||||
namespace: capsule-system
|
||||
path: /pods
|
||||
path: /nodes
|
||||
failurePolicy: Fail
|
||||
name: pods.capsule.clastix.io
|
||||
name: nodes.capsule.clastix.io
|
||||
namespaceSelector:
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- UPDATE
|
||||
resources:
|
||||
- nodes
|
||||
sideEffects: None
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
service:
|
||||
name: capsule-webhook-service
|
||||
namespace: capsule-system
|
||||
path: /pods
|
||||
failurePolicy: Fail
|
||||
name: pods.capsule.clastix.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
|
||||
@@ -7,4 +7,4 @@ kind: Kustomization
|
||||
images:
|
||||
- name: controller
|
||||
newName: quay.io/clastix/capsule
|
||||
newTag: v0.1.0
|
||||
newTag: v0.1.1-rc0
|
||||
|
||||
@@ -118,6 +118,25 @@ webhooks:
|
||||
resources:
|
||||
- networkpolicies
|
||||
sideEffects: None
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
service:
|
||||
name: webhook-service
|
||||
namespace: system
|
||||
path: /nodes
|
||||
failurePolicy: Fail
|
||||
name: nodes.capsule.clastix.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- UPDATE
|
||||
resources:
|
||||
- nodes
|
||||
sideEffects: None
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
|
||||
@@ -23,13 +23,13 @@
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
- op: add
|
||||
path: /webhooks/5/namespaceSelector
|
||||
path: /webhooks/6/namespaceSelector
|
||||
value:
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
operator: Exists
|
||||
- op: add
|
||||
path: /webhooks/6/namespaceSelector
|
||||
path: /webhooks/7/namespaceSelector
|
||||
value:
|
||||
matchExpressions:
|
||||
- key: capsule.clastix.io/tenant
|
||||
@@ -43,12 +43,12 @@
|
||||
- op: add
|
||||
path: /webhooks/3/rules/0/scope
|
||||
value: Namespaced
|
||||
- op: add
|
||||
path: /webhooks/4/rules/0/scope
|
||||
value: Namespaced
|
||||
- op: add
|
||||
path: /webhooks/5/rules/0/scope
|
||||
value: Namespaced
|
||||
- op: add
|
||||
path: /webhooks/6/rules/0/scope
|
||||
value: Namespaced
|
||||
- op: add
|
||||
path: /webhooks/7/rules/0/scope
|
||||
value: Namespaced
|
||||
|
||||
@@ -35,7 +35,7 @@ type CAReconciler struct {
|
||||
|
||||
func (r *CAReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&corev1.Secret{}, forOptionPerInstanceName(caSecretName)).
|
||||
For(&corev1.Secret{}, forOptionPerInstanceName(CASecretName)).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,6 @@ const (
|
||||
certSecretKey = "tls.crt"
|
||||
privateKeySecretKey = "tls.key"
|
||||
|
||||
caSecretName = "capsule-ca"
|
||||
CASecretName = "capsule-ca"
|
||||
tlsSecretName = "capsule-tls"
|
||||
)
|
||||
|
||||
@@ -22,10 +22,10 @@ func getCertificateAuthority(client client.Client, namespace string) (ca cert.CA
|
||||
|
||||
err = client.Get(context.TODO(), types.NamespacedName{
|
||||
Namespace: namespace,
|
||||
Name: caSecretName,
|
||||
Name: CASecretName,
|
||||
}, instance)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("missing secret %s, cannot reconcile", caSecretName)
|
||||
return nil, fmt.Errorf("missing secret %s, cannot reconcile", CASecretName)
|
||||
}
|
||||
|
||||
if instance.Data == nil {
|
||||
|
||||
@@ -14,8 +14,8 @@ type EndpointSlicesLabelsReconciler struct {
|
||||
abstractServiceLabelsReconciler
|
||||
|
||||
Log logr.Logger
|
||||
VersionMinor int
|
||||
VersionMajor int
|
||||
VersionMinor uint
|
||||
VersionMajor uint
|
||||
}
|
||||
|
||||
func (r *EndpointSlicesLabelsReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
|
||||
@@ -68,28 +68,22 @@ func (r Manager) Reconcile(ctx context.Context, request ctrl.Request) (result ct
|
||||
return
|
||||
}
|
||||
|
||||
if instance.Spec.NetworkPolicies != nil {
|
||||
r.Log.Info("Starting processing of Network Policies", "items", len(instance.Spec.NetworkPolicies.Items))
|
||||
if err = r.syncNetworkPolicies(instance); err != nil {
|
||||
r.Log.Error(err, "Cannot sync NetworkPolicy items")
|
||||
return
|
||||
}
|
||||
r.Log.Info("Starting processing of Network Policies")
|
||||
if err = r.syncNetworkPolicies(instance); err != nil {
|
||||
r.Log.Error(err, "Cannot sync NetworkPolicy items")
|
||||
return
|
||||
}
|
||||
|
||||
if instance.Spec.LimitRanges != nil {
|
||||
r.Log.Info("Starting processing of Limit Ranges", "items", len(instance.Spec.LimitRanges.Items))
|
||||
if err = r.syncLimitRanges(instance); err != nil {
|
||||
r.Log.Error(err, "Cannot sync LimitRange items")
|
||||
return
|
||||
}
|
||||
r.Log.Info("Starting processing of Limit Ranges", "items", len(instance.Spec.LimitRanges.Items))
|
||||
if err = r.syncLimitRanges(instance); err != nil {
|
||||
r.Log.Error(err, "Cannot sync LimitRange items")
|
||||
return
|
||||
}
|
||||
|
||||
if instance.Spec.ResourceQuota != nil {
|
||||
r.Log.Info("Starting processing of Resource Quotas", "items", len(instance.Spec.ResourceQuota.Items))
|
||||
if err = r.syncResourceQuotas(instance); err != nil {
|
||||
r.Log.Error(err, "Cannot sync ResourceQuota items")
|
||||
return
|
||||
}
|
||||
r.Log.Info("Starting processing of Resource Quotas", "items", len(instance.Spec.ResourceQuota.Items))
|
||||
if err = r.syncResourceQuotas(instance); err != nil {
|
||||
r.Log.Error(err, "Cannot sync ResourceQuota items")
|
||||
return
|
||||
}
|
||||
|
||||
r.Log.Info("Ensuring additional RoleBindings for owner")
|
||||
|
||||
8
docs/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
*.log
|
||||
.cache
|
||||
.DS_Store
|
||||
src/.temp
|
||||
node_modules
|
||||
dist
|
||||
.env
|
||||
.env.*
|
||||
12
docs/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Capsule Documentation
|
||||
|
||||
1. Ensure to have [`yarn`](https://classic.yarnpkg.com/lang/en/docs/install/#debian-stable) installed in your path.
|
||||
2. `yarn install`
|
||||
|
||||
## Local development
|
||||
|
||||
```shell
|
||||
yarn develop
|
||||
```
|
||||
|
||||
This will create a local webserver listening on `localhost:8080` with hot-reload of your local changes.
|
||||
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
BIN
docs/content/assets/dev-env.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
369
docs/content/dev-guide.md
Normal file
@@ -0,0 +1,369 @@
|
||||
# Capsule Development Guide
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Tools
|
||||
|
||||
Make sure you have these tools installed:
|
||||
|
||||
- [Go 1.16+](https://golang.org/dl/)
|
||||
- [Operator SDK 1.7.2+](https://github.com/operator-framework/operator-sdk), or [Kubebuilder](https://github.com/kubernetes-sigs/kubebuilder)
|
||||
- [KinD](https://github.com/kubernetes-sigs/kind) or [k3d](https://k3d.io/), with `kubectl`
|
||||
- [ngrok](https://ngrok.com/) (if you want to run locally with remote Kubernetes)
|
||||
- [golangci-lint](https://github.com/golangci/golangci-lint)
|
||||
- OpenSSL
|
||||
|
||||
### Kubernetes Cluster
|
||||
|
||||
A lightweight Kubernetes within your laptop can be very handy for Kubernetes-native development like Capsule.
|
||||
|
||||
#### By `k3d`
|
||||
|
||||
```shell
|
||||
# Install K3d cli by brew in Mac, or your preferred way
|
||||
$ brew install k3d
|
||||
|
||||
# Export your laptop's IP, e.g. retrieving it by: ifconfig
|
||||
# Do change this IP to yours
|
||||
$ export LAPTOP_HOST_IP=192.168.10.101
|
||||
|
||||
# Spin up a bare minimum cluster
|
||||
# Refer to here for more options: https://k3d.io/v4.4.8/usage/commands/k3d_cluster_create/
|
||||
$ k3d cluster create k3s-capsule --servers 1 --agents 1 --no-lb --k3s-server-arg --tls-san=${LAPTOP_HOST_IP}
|
||||
|
||||
# Get Kubeconfig
|
||||
$ k3d kubeconfig get k3s-capsule > /tmp/k3s-capsule && export KUBECONFIG="/tmp/k3s-capsule"
|
||||
|
||||
# This will create a cluster with 1 server and 1 worker node
|
||||
$ kubectl get nodes
|
||||
NAME STATUS ROLES AGE VERSION
|
||||
k3d-k3s-capsule-server-0 Ready control-plane,master 2m13s v1.21.2+k3s1
|
||||
k3d-k3s-capsule-agent-0 Ready <none> 2m3s v1.21.2+k3s1
|
||||
|
||||
# Or 2 Docker containers if you view it from Docker perspective
|
||||
$ docker ps
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
5c26ad840c62 rancher/k3s:v1.21.2-k3s1 "/bin/k3s agent" 53 seconds ago Up 45 seconds k3d-k3s-capsule-agent-0
|
||||
753998879b28 rancher/k3s:v1.21.2-k3s1 "/bin/k3s server --t…" 53 seconds ago Up 51 seconds 0.0.0.0:49708->6443/tcp k3d-k3s-capsule-server-0
|
||||
```
|
||||
|
||||
#### By `kind`
|
||||
|
||||
```shell
|
||||
# # Install kind cli by brew in Mac, or your preferred way
|
||||
$ brew install kind
|
||||
|
||||
# Prepare a kind config file with necessary customization
|
||||
$ cat > kind.yaml <<EOF
|
||||
kind: Cluster
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
networking:
|
||||
apiServerAddress: "0.0.0.0"
|
||||
nodes:
|
||||
- role: control-plane
|
||||
kubeadmConfigPatches:
|
||||
- |
|
||||
kind: ClusterConfiguration
|
||||
metadata:
|
||||
name: config
|
||||
apiServer:
|
||||
certSANs:
|
||||
- localhost
|
||||
- 127.0.0.1
|
||||
- kubernetes
|
||||
- kubernetes.default.svc
|
||||
- kubernetes.default.svc.cluster.local
|
||||
- kind
|
||||
- 0.0.0.0
|
||||
- ${LAPTOP_HOST_IP}
|
||||
- role: worker
|
||||
EOF
|
||||
|
||||
# Spin up a bare minimum cluster with 1 master 1 worker node
|
||||
$ kind create cluster --name kind-capsule --config kind.yaml
|
||||
|
||||
# This will create a cluster with 1 server and 1 worker node
|
||||
$ kubectl get nodes
|
||||
NAME STATUS ROLES AGE VERSION
|
||||
kind-capsule-control-plane Ready control-plane,master 84s v1.21.1
|
||||
kind-capsule-worker Ready <none> 56s v1.21.1
|
||||
|
||||
# Or 2 Docker containers if you view it from Docker perspective
|
||||
$ docker ps
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
7b329fd3a838 kindest/node:v1.21.1 "/usr/local/bin/entr…" About a minute ago Up About a minute 0.0.0.0:54894->6443/tcp kind-capsule-control-plane
|
||||
7d50f1633555 kindest/node:v1.21.1 "/usr/local/bin/entr…" About a minute ago Up About a minute kind-capsule-worker
|
||||
```
|
||||
|
||||
## Fork & clone the repository
|
||||
|
||||
The `fork-clone-contribute-pr` flow is common for contributing to OSS projects like Kubernetes, Capsule.
|
||||
|
||||
Let's assume you've forked it into your GitHub namespace, say `myuser`, and then you can clone it with Git protocol.
|
||||
Do remember to change the `myuser` to yours.
|
||||
|
||||
```shell
|
||||
$ git clone git@github.com:myuser/capsule.git && cd capsule
|
||||
```
|
||||
|
||||
It's a good practice to add the upsteam as the remote too so we can easily fetch and merge the upstream to our fork:
|
||||
|
||||
```shell
|
||||
$ git remote add upstream https://github.com/clastix/capsule.git
|
||||
$ git remote -vv
|
||||
origin git@github.com:myuser/capsule.git (fetch)
|
||||
origin git@github.com:myuser/capsule.git (push)
|
||||
upstream https://github.com/clastix/capsule.git (fetch)
|
||||
upstream https://github.com/clastix/capsule.git (push)
|
||||
```
|
||||
|
||||
## Build & deploy Capsule
|
||||
|
||||
```shell
|
||||
# Download the project dependencies
|
||||
$ go mod download
|
||||
|
||||
# Build the Capsule image
|
||||
$ make docker-build
|
||||
|
||||
# Retrieve the built image version
|
||||
$ export CAPSULE_IMAGE_VESION=`docker images --format '{{.Tag}}' quay.io/clastix/capsule`
|
||||
|
||||
# If k3s, load the image into cluster by
|
||||
$ k3d image import --cluster k3s-capsule capsule quay.io/clastix/capsule:${CAPSULE_IMAGE_VESION}
|
||||
# If Kind, load the image into cluster by
|
||||
$ kind load docker-image --name kind-capsule quay.io/clastix/capsule:${CAPSULE_IMAGE_VESION}
|
||||
|
||||
# deploy all the required manifests
|
||||
# Note: 1) please retry if you saw errors; 2) if you want to clean it up first, run: make remove
|
||||
$ make deploy
|
||||
|
||||
# Make sure the controller is running
|
||||
$ kubectl get pod -n capsule-system
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
capsule-controller-manager-5c6b8445cf-566dc 1/1 Running 0 23s
|
||||
|
||||
# Check the logs if needed
|
||||
$ kubectl -n capsule-system logs --all-containers -l control-plane=controller-manager
|
||||
|
||||
# You may have a try to deploy a Tenant too to make sure it works end to end
|
||||
$ kubectl apply -f - <<EOF
|
||||
apiVersion: capsule.clastix.io/v1beta1
|
||||
kind: Tenant
|
||||
metadata:
|
||||
name: oil
|
||||
spec:
|
||||
owners:
|
||||
- name: alice
|
||||
kind: User
|
||||
- name: system:serviceaccount:capsule-system:default
|
||||
kind: ServiceAccount
|
||||
EOF
|
||||
|
||||
# There shouldn't be any errors and you should see the newly created tenant
|
||||
$ kubectl get tenants
|
||||
NAME STATE NAMESPACE QUOTA NAMESPACE COUNT NODE SELECTOR AGE
|
||||
oil Active 0 14s
|
||||
```
|
||||
|
||||
If you want to test namespace creation or such stuff, make sure to use impersonation:
|
||||
|
||||
```sh
|
||||
$ kubectl ... --as system:serviceaccount:capsule-system:default --as-group capsule.clastix.io
|
||||
```
|
||||
|
||||
As of now, a complete Capsule environment has been set up in `kind`- or `k3d`-powered cluster, and the `capsule-controller-manager` is running as a deployment serving as:
|
||||
|
||||
- The reconcilers for CRDs and;
|
||||
- A series of webhooks
|
||||
|
||||
## Set up development env
|
||||
|
||||
During development, we prefer that the code is running within our IDE locally, instead of running as the normal Pod(s) within the Kubernetes cluster.
|
||||
|
||||
Such a setup can be illustrated as below diagram:
|
||||
|
||||

|
||||
|
||||
To achieve that, there are some necessary steps we need to walk through, which have been made as a `make` target within our `Makefile`.
|
||||
|
||||
So the TL;DR answer is:
|
||||
|
||||
```shell
|
||||
# If you haven't installed or run `make deploy` before, do it first
|
||||
# Note: please retry if you saw errors
|
||||
$ make deploy
|
||||
|
||||
# To retrieve your laptop's IP and execute `make dev-setup` to setup dev env
|
||||
# For example: LAPTOP_HOST_IP=192.168.10.101 make dev-setup
|
||||
$ LAPTOP_HOST_IP="<YOUR_LAPTOP_IP>" make dev-setup
|
||||
```
|
||||
|
||||
|
||||
This is a very common setup for typical Kubernetes Operator development so we'd better walk them through with more details here.
|
||||
|
||||
1. Scaling down the deployed Pod(s) to 0
|
||||
|
||||
We need to scale the existing replicas of `capsule-controller-manager` to 0 to avoid reconciliation competition between the Pod(s) and the code running outside of the cluster, in our preferred IDE for example.
|
||||
|
||||
```shell
|
||||
$ kubectl -n capsule-system scale deployment capsule-controller-manager --replicas=0
|
||||
deployment.apps/capsule-controller-manager scaled
|
||||
```
|
||||
|
||||
2. Preparing TLS certificate for the webhooks
|
||||
|
||||
Running webhooks requires TLS, we can prepare the TLS key pair in our development env to handle HTTPS requests.
|
||||
|
||||
```shell
|
||||
# Prepare a simple OpenSSL config file
|
||||
# Do remember to export LAPTOP_HOST_IP before running this command
|
||||
$ cat > _tls.cnf <<EOF
|
||||
[ req ]
|
||||
default_bits = 4096
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = req_ext
|
||||
[ req_distinguished_name ]
|
||||
countryName = SG
|
||||
stateOrProvinceName = SG
|
||||
localityName = SG
|
||||
organizationName = CAPSULE
|
||||
commonName = CAPSULE
|
||||
[ req_ext ]
|
||||
subjectAltName = @alt_names
|
||||
[alt_names]
|
||||
IP.1 = ${LAPTOP_HOST_IP}
|
||||
EOF
|
||||
|
||||
# Create this dir to mimic the Pod mount point
|
||||
$ mkdir -p /tmp/k8s-webhook-server/serving-certs
|
||||
|
||||
# Generate the TLS cert/key under /tmp/k8s-webhook-server/serving-certs
|
||||
$ openssl req -newkey rsa:4096 -days 3650 -nodes -x509 \
|
||||
-subj "/C=SG/ST=SG/L=SG/O=CAPSULE/CN=CAPSULE" \
|
||||
-extensions req_ext \
|
||||
-config _tls.cnf \
|
||||
-keyout /tmp/k8s-webhook-server/serving-certs/tls.key \
|
||||
-out /tmp/k8s-webhook-server/serving-certs/tls.crt
|
||||
|
||||
# Clean it up
|
||||
$ rm -f _tls.cnf
|
||||
```
|
||||
|
||||
3. Patching the Webhooks
|
||||
|
||||
By default, the webhooks will be registered with the services, which will route to the Pods, inside the cluster.
|
||||
|
||||
We need to _delegate_ the controllers' and webbooks' services to the code running in our IDE by patching the `MutatingWebhookConfiguration` and `ValidatingWebhookConfiguration`.
|
||||
|
||||
```shell
|
||||
# Export your laptop's IP with the 9443 port exposed by controllers/webhooks' services
|
||||
$ export WEBHOOK_URL="https://${LAPTOP_HOST_IP}:9443"
|
||||
|
||||
# Export the cert we just generated as the CA bundle for webhook TLS
|
||||
$ export CA_BUNDLE=`openssl base64 -in /tmp/k8s-webhook-server/serving-certs/tls.crt | tr -d '\n'`
|
||||
|
||||
# Patch the MutatingWebhookConfiguration webhook
|
||||
$ kubectl patch MutatingWebhookConfiguration capsule-mutating-webhook-configuration \
|
||||
--type='json' -p="[\
|
||||
{'op': 'replace', 'path': '/webhooks/0/clientConfig', 'value':{'url':\"${WEBHOOK_URL}/mutate-v1-namespace-owner-reference\",'caBundle':\"${CA_BUNDLE}\"}}\
|
||||
]"
|
||||
|
||||
# Verify it if you want
|
||||
$ kubectl get MutatingWebhookConfiguration capsule-mutating-webhook-configuration -o yaml
|
||||
|
||||
# Patch the ValidatingWebhookConfiguration webhooks
|
||||
# Note: there is a list of validating webhook endpoints, not just one
|
||||
$ kubectl patch ValidatingWebhookConfiguration capsule-validating-webhook-configuration \
|
||||
--type='json' -p="[\
|
||||
{'op': 'replace', 'path': '/webhooks/0/clientConfig', 'value':{'url':\"${WEBHOOK_URL}/cordoning\",'caBundle':\"${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/1/clientConfig', 'value':{'url':\"${WEBHOOK_URL}/ingresses\",'caBundle':\"${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/2/clientConfig', 'value':{'url':\"${WEBHOOK_URL}/namespaces\",'caBundle':\"${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/3/clientConfig', 'value':{'url':\"${WEBHOOK_URL}/networkpolicies\",'caBundle':\"${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/4/clientConfig', 'value':{'url':\"${WEBHOOK_URL}/pods\",'caBundle':\"${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/5/clientConfig', 'value':{'url':\"${WEBHOOK_URL}/persistentvolumeclaims\",'caBundle':\"${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/6/clientConfig', 'value':{'url':\"${WEBHOOK_URL}/services\",'caBundle':\"${CA_BUNDLE}\"}},\
|
||||
{'op': 'replace', 'path': '/webhooks/7/clientConfig', 'value':{'url':\"${WEBHOOK_URL}/tenants\",'caBundle':\"${CA_BUNDLE}\"}}\
|
||||
]"
|
||||
|
||||
# Verify it if you want
|
||||
$ kubectl get ValidatingWebhookConfiguration capsule-validating-webhook-configuration -o yaml
|
||||
```
|
||||
|
||||
## Run Capsule outside the cluster
|
||||
|
||||
Now we can run Capsule controllers with webhooks outside of the Kubernetes cluster:
|
||||
|
||||
```shell
|
||||
$ export NAMESPACE=capsule-system && export TMPDIR=/tmp/
|
||||
$ go run .
|
||||
```
|
||||
|
||||
To verify that, we can open a new console and create a new Tenant:
|
||||
|
||||
```shell
|
||||
$ kubectl apply -f - <<EOF
|
||||
apiVersion: capsule.clastix.io/v1beta1
|
||||
kind: Tenant
|
||||
metadata:
|
||||
name: gas
|
||||
spec:
|
||||
owners:
|
||||
- name: alice
|
||||
kind: User
|
||||
EOF
|
||||
```
|
||||
|
||||
We should see output like:
|
||||
```log
|
||||
tenant.capsule.clastix.io/gas created
|
||||
```
|
||||
|
||||
And could see logs in the `make run` console like:
|
||||
```log
|
||||
...
|
||||
{"level":"info","ts":"2021-09-28T21:10:30.520+0800","logger":"controllers.Tenant","msg":"Ensuring all Namespaces are collected","Request.Name":"gas"}
|
||||
{"level":"info","ts":"2021-09-28T21:10:30.527+0800","logger":"controllers.Tenant","msg":"Starting processing of Namespaces","Request.Name":"gas","items":0}
|
||||
{"level":"info","ts":"2021-09-28T21:10:30.527+0800","logger":"controllers.Tenant","msg":"Ensuring additional RoleBindings for owner","Request.Name":"gas"}
|
||||
{"level":"info","ts":"2021-09-28T21:10:30.527+0800","logger":"controllers.Tenant","msg":"Ensuring RoleBinding for owner","Request.Name":"gas"}
|
||||
{"level":"info","ts":"2021-09-28T21:10:30.527+0800","logger":"controllers.Tenant","msg":"Ensuring Namespace count","Request.Name":"gas"}
|
||||
{"level":"info","ts":"2021-09-28T21:10:30.533+0800","logger":"controllers.Tenant","msg":"Tenant reconciling completed","Request.Name":"gas"}
|
||||
{"level":"info","ts":"2021-09-28T21:10:30.540+0800","logger":"controllers.Tenant","msg":"Ensuring all Namespaces are collected","Request.Name":"gas"}
|
||||
{"level":"info","ts":"2021-09-28T21:10:30.547+0800","logger":"controllers.Tenant","msg":"Starting processing of Namespaces","Request.Name":"gas","items":0}
|
||||
{"level":"info","ts":"2021-09-28T21:10:30.547+0800","logger":"controllers.Tenant","msg":"Ensuring additional RoleBindings for owner","Request.Name":"gas"}
|
||||
{"level":"info","ts":"2021-09-28T21:10:30.547+0800","logger":"controllers.Tenant","msg":"Ensuring RoleBinding for owner","Request.Name":"gas"}
|
||||
{"level":"info","ts":"2021-09-28T21:10:30.547+0800","logger":"controllers.Tenant","msg":"Ensuring Namespace count","Request.Name":"gas"}
|
||||
{"level":"info","ts":"2021-09-28T21:10:30.554+0800","logger":"controllers.Tenant","msg":"Tenant reconciling completed","Request.Name":"gas"}
|
||||
```
|
||||
|
||||
## Work in your preferred IDE
|
||||
|
||||
Now it's time to work through our familiar inner loop for development in our preferred IDE.
|
||||
|
||||
For example, if you're using [Visual Studio Code](https://code.visualstudio.com), this `launch.json` file can be a good start.
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}",
|
||||
"args": [
|
||||
"--zap-encoder=console",
|
||||
"--zap-log-level=debug",
|
||||
"--configuration-name=capsule-default"
|
||||
],
|
||||
"env": {
|
||||
"NAMESPACE": "capsule-system",
|
||||
"TMPDIR": "/tmp/"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Please refer to [contributing](/docs/contributing) for more details while contributing.
|
||||
@@ -3,6 +3,6 @@
|
||||
|
||||
Currently, the Capsule ecosystem comprises the following:
|
||||
|
||||
* [Capsule Operator](./operator/overview.md)
|
||||
* [Capsule Proxy](./proxy/overview.md)
|
||||
* [Capsule Lens extension](./lens-extension/overview.md)
|
||||
* [Capsule Operator](/docs/operator/overview)
|
||||
* [Capsule Proxy](/docs/proxy/overview)
|
||||
* [Capsule Lens extension](/docs/lens-extension/overview)
|
||||
63
docs/content/operator/contributing.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# How to contribute to Capsule
|
||||
|
||||
First, thanks for your interest in Capsule, any contribution is welcome!
|
||||
|
||||
## Development environment setup
|
||||
|
||||
The first step is to set up your local development environment.
|
||||
|
||||
Please follow the [Capsule Development Guide](/docs/dev-guide) for details.
|
||||
|
||||
## Code convention
|
||||
|
||||
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/capsule/blob/master/.golangci.yml)
|
||||
|
||||
### goimports
|
||||
|
||||
Also, the Go import statements must be sorted following the best practice:
|
||||
|
||||
```
|
||||
<STANDARD LIBRARY>
|
||||
|
||||
<EXTERNAL PACKAGES>
|
||||
|
||||
<LOCAL PACKAGES>
|
||||
```
|
||||
|
||||
To help you out you can use the _Make_ recipe `goimports`
|
||||
|
||||
```
|
||||
# make goimports
|
||||
goimports -w -l -local "github.com/clastix/capsule" .
|
||||
```
|
||||
|
||||
### Commits
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
### Miscellanea
|
||||
|
||||
Please, add a new single line at end of any file as the current coding style.
|
||||
@@ -68,7 +68,7 @@ Users authenticated through an _OIDC token_ must have in their token:
|
||||
]
|
||||
```
|
||||
|
||||
The [hack/create-user.sh](../../hack/create-user.sh) can help you set up a dummy `kubeconfig` for the `alice` user acting as owner of a tenant called `oil`
|
||||
The [hack/create-user.sh](https://github.com/clastix/capsule/blob/master/hack/create-user.sh) can help you set up a dummy `kubeconfig` for the `alice` user acting as owner of a tenant called `oil`
|
||||
|
||||
```bash
|
||||
./hack/create-user.sh alice oil
|
||||
@@ -107,4 +107,4 @@ Error from server (Forbidden): pods is forbidden: User "alice" cannot list resou
|
||||
```
|
||||
|
||||
# What’s next
|
||||
The Tenant Owners have full administrative permissions limited to only the namespaces in the assigned tenant. However, their permissions can be controlled by the Cluster Admin by setting rules and policies on the assigned tenant. See the [use cases](./use-cases/overview.md) page for more getting more cool things you can do with Capsule.
|
||||
The Tenant Owners have full administrative permissions limited to only the namespaces in the assigned tenant. However, their permissions can be controlled by the Cluster Admin by setting rules and policies on the assigned tenant. See the [use cases](/docs/operator/use-cases/overview) page for more getting more cool things you can do with Capsule.
|
||||
@@ -9,7 +9,7 @@ Capsule Operator can be easily installed on a Managed Kubernetes Service. Since
|
||||
- MutatingAdmissionWebhook
|
||||
- ValidatingAdmissionWebhook
|
||||
|
||||
* [AWS EKS](./aws-eks.md)
|
||||
* [AWS EKS](/docs/operator/managed-kubernetes/aws-eks)
|
||||
* CoAKS - Capsule over Azure Kubernetes Service
|
||||
* Google Cloud GKE
|
||||
* IBM Cloud
|
||||
30
docs/content/operator/mtb/sig-multitenancy-bench.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Meet the multi-tenancy benchmark MTB
|
||||
Actually, there's no yet a real standard for the multi-tenancy model in Kubernetes, although the [SIG multi-tenancy group](https://github.com/kubernetes-sigs/multi-tenancy) is working on that. SIG multi-tenancy drafted a generic validation schema appliable to generic multi-tenancy projects. Multi-Tenancy Benchmarks [MTB](https://github.com/kubernetes-sigs/multi-tenancy/tree/master/benchmarks) are guidelines for multi-tenant configuration of Kubernetes clusters. Capsule is an open source multi-tenancy operator and we decided to meet the requirements of MTB.
|
||||
|
||||
> N.B. At the time of writing, the MTB is in development and not ready for usage. Strictly speaking, we do not claim official conformance to MTB, but just to adhere to the multi-tenancy requirements and best practices promoted by MTB.
|
||||
|
||||
|MTB Benchmark |MTB Profile|Capsule Version|Conformance|Notes |
|
||||
|--------------|-----------|---------------|-----------|-------|
|
||||
|[Block access to cluster resources](/docs/operator/mtb/block-access-to-cluster-resources)|L1|v0.1.0|✓|---|
|
||||
|[Block access to multitenant resources](/docs/operator/mtb/block-access-to-multitenant-resources)|L1|v0.1.0|✓|---|
|
||||
|[Block access to other tenant resources](/docs/operator/mtb/block-access-to-other-tenant-resources)|L1|v0.1.0|✓|MTB draft|
|
||||
|[Block add capabilities](/docs/operator/mtb/block-add-capabilities)|L1|v0.1.0|✓|---|
|
||||
|[Require always imagePullPolicy](/docs/operator/mtb/require-always-imagepullpolicy)|L1|v0.1.0|✓|---|
|
||||
|[Require run as non-root user](/docs/operator/mtb/require-run-as-non-root-user)|L1|v0.1.0|✓|---|
|
||||
|[Block privileged containers](/docs/operator/mtb/block-privileged-containers)|L1|v0.1.0|✓|---|
|
||||
|[Block privilege escalation](/docs/operator/mtb/block-privilege-escalation)|L1|v0.1.0|✓|---|
|
||||
|[Configure namespace resource quotas](/docs/operator/mtb/configure-namespace-resource-quotas)|L1|v0.1.0|✓|---|
|
||||
|[Block modification of resource quotas](/docs/operator/mtb/block-modification-of-resource-quotas)|L1|v0.1.0|✓|---|
|
||||
|[Configure namespace object limits](/docs/operator/mtb/configure-namespace-object-limits)|L1|v0.1.0|✓|---|
|
||||
|[Block use of host path volumes](/docs/operator/mtb/block-use-of-host-path-volumes)|L1|v0.1.0|✓|---|
|
||||
|[Block use of host networking and ports](/docs/operator/mtb/block-use-of-host-networking-and-ports)|L1|v0.1.0|✓|---|
|
||||
|[Block use of host PID](/docs/operator/mtb/block-use-of-host-pid)|L1|v0.1.0|✓|---|
|
||||
|[Block use of host IPC](/docs/operator/mtb/block-use-of-host-ipc)|L1|v0.1.0|✓|---|
|
||||
|[Block use of NodePort services](/docs/operator/mtb/block-use-of-nodeport-services)|L1|v0.1.0|✓|---|
|
||||
|[Require PersistentVolumeClaim for storage](/docs/operator/mtb/require-persistentvolumeclaim-for-storage)|L1|v0.1.0|✓|MTB draft|
|
||||
|[Require PV reclaim policy of delete](/docs/operator/mtb/require-reclaim-policy-of-delete)|L1|v0.1.0|✓|MTB draft|
|
||||
|[Block use of existing PVs](/docs/operator/mtb/block-use-of-existing-persistent-volumes)|L1|v0.1.0|✓|MTB draft|
|
||||
|[Block network access across tenant namespaces](/docs/operator/mtb/block-network-access-across-tenant-namespaces)|L1|v0.1.0|✓|MTB draft|
|
||||
|[Allow self-service management of Network Policies](/docs/operator/mtb/allow-self-service-management-of-network-policies)|L2|v0.1.0|✓|---|
|
||||
|[Allow self-service management of Roles](/docs/operator/mtb/allow-self-service-management-of-roles)|L2|v0.1.0|✓|MTB draft|
|
||||
|[Allow self-service management of Role Bindings](/docs/operator/mtb/allow-self-service-management-of-rolebindings)|L2|v0.1.0|✓|MTB draft|
|
||||
10
docs/content/operator/overview.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Kubernetes Operator
|
||||
|
||||
* [Getting Started](/docs/operator/getting-started)
|
||||
* [Use Cases](/docs/operator/use-cases/overview)
|
||||
* [SIG Multi-tenancy benchmark](/docs/operator/mtb/sig-multitenancy-bench)
|
||||
* [Run on Managed Kubernetes Services](/docs/operator/managed-kubernetes/overview)
|
||||
* [Monitoring Capsule](/docs/operator/monitoring)
|
||||
* [References](/docs/operator/references)
|
||||
* [Contributing](/docs/operator/contributing)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Reference
|
||||
|
||||
* [Custom Resource Definition](#customer-resource-definition)
|
||||
* [Capsule Configuration](#capsule-configuration)
|
||||
* [Capsule Permissions](#capsule-permissions)
|
||||
* [Admission Controllers](#admission-controller)
|
||||
* [Command Options](#command-options)
|
||||
* [Created Resources](#created-resources)
|
||||
* [Custom Resource Definition](/docs/operator/references/#customer-resource-definition)
|
||||
* [Capsule Configuration](/docs/operator/references/#capsule-configuration)
|
||||
* [Capsule Permissions](/docs/operator/references/#capsule-permissions)
|
||||
* [Admission Controllers](/docs/operator/references/#admission-controller)
|
||||
* [Command Options](/docs/operator/references/#command-options)
|
||||
* [Created Resources](/docs/operator/references/#created-resources)
|
||||
|
||||
## Custom Resource Definition
|
||||
|
||||
@@ -13,7 +13,7 @@ Capsule operator uses a Custom Resources Definition (CRD) for _Tenants_. In Caps
|
||||
|
||||
You can learn about tenant CRD by the `kubectl explain` command:
|
||||
|
||||
```command
|
||||
```
|
||||
kubectl explain tenant
|
||||
|
||||
KIND: Tenant
|
||||
@@ -44,7 +44,7 @@ FIELDS:
|
||||
|
||||
For Tenant spec:
|
||||
|
||||
```command
|
||||
```
|
||||
kubectl explain tenant.spec
|
||||
|
||||
KIND: Tenant
|
||||
@@ -124,7 +124,7 @@ FIELDS:
|
||||
|
||||
and Tenant status:
|
||||
|
||||
```command
|
||||
```
|
||||
kubectl explain tenant.status
|
||||
KIND: Tenant
|
||||
VERSION: capsule.clastix.io/v1beta1
|
||||
@@ -49,4 +49,4 @@ silver Active 2 3d13h
|
||||
|
||||
# What’s next
|
||||
|
||||
See how Bill, the cluster admin, can prevent creating services with specific service types. [Disabling Service Types](./service-type.md).
|
||||
See how Bill, the cluster admin, can prevent creating services with specific service types. [Disabling Service Types](/docs/operator/use-cases/service-type).
|
||||
@@ -107,4 +107,4 @@ Error from server (Cannot exceed Namespace quota: please, reach out to the syste
|
||||
The enforcement on the maximum number of namespaces per Tenant is the responsibility of the Capsule controller via its Dynamic Admission Webhook capability.
|
||||
|
||||
# What’s next
|
||||
See how Alice, the tenant owner, can assign different user roles in the tenant. [Assign permissions](./permissions.md).
|
||||
See how Alice, the tenant owner, can assign different user roles in the tenant. [Assign permissions](/docs/operator/use-cases/permissions).
|
||||
@@ -75,4 +75,4 @@ With the above example, Capsule is leaving the tenant owner to create namespaced
|
||||
> Take Note: a tenant owner having the admin scope on its namespaces only, does not have the permission to create Custom Resources Definitions (CRDs) because this requires a cluster admin permission level. Only Bill, the cluster admin, can create CRDs. This is a known limitation of any multi-tenancy environment based on a single Kubernetes cluster.
|
||||
|
||||
# What’s next
|
||||
See how Bill, the cluster admin, can set taints on Alice's namespaces. [Taint namespaces](./taint-namespaces.md).
|
||||
See how Bill, the cluster admin, can set taints on Alice's namespaces. [Taint namespaces](/docs/operator/use-cases/taint-namespaces).
|
||||
29
docs/content/operator/use-cases/deny-wildcard-hostnames.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Deny Wildcard Hostnames
|
||||
|
||||
Bill, the cluster admin, can deny the use of wildcard hostnames.
|
||||
|
||||
Let's assume that we had a big organization, having a domain `bigorg.com` and there are two tenants, `gas` and `oil`.
|
||||
|
||||
As a tenant-owner of `gas`, Alice create ingress with the host like `- host: "*.bigorg.com"`. That can lead to big problems for the `oil` tenant because Alice can deliberately create ingress with host: `oil.bigorg.com`.
|
||||
|
||||
To avoid this kind of problems, Bill can deny the use of wildcard hostnames in the following way:
|
||||
|
||||
```yaml
|
||||
kubectl apply -f - << EOF
|
||||
apiVersion: capsule.clastix.io/v1beta1
|
||||
kind: Tenant
|
||||
metadata:
|
||||
name: gas
|
||||
annotations:
|
||||
capsule.clastix.io/deny-wildcard: true
|
||||
spec:
|
||||
owners:
|
||||
- name: alice
|
||||
kind: User
|
||||
EOF
|
||||
```
|
||||
|
||||
Doing this, Alice will not be able to use `oil.bigorg.com`, being the tenant-owner of `gas`.
|
||||
|
||||
# What’s next
|
||||
See how Bill, the cluster admin can protect specific labels and annotations on Nodes from modifications by Tenant Owners. [Denying specific user-defined labels or annotations on Nodes](/docs/operator/use-cases/node-labels-and-annotations).
|
||||
@@ -77,4 +77,4 @@ EOF
|
||||
When a collision is detected at scope defined by `spec.ingressOptions.hostnameCollisionScope`, the creation of the Ingress resource will be rejected by the Validation Webhook enforcing it. When `hostnameCollisionScope=Disabled`, no collision detection is made at all.
|
||||
|
||||
# What’s next
|
||||
See how Bill, the cluster admin, can assign a Storage Class to Alice's tenant. [Assign Storage Classes](./storage-classes.md).
|
||||
See how Bill, the cluster admin, can assign a Storage Class to Alice's tenant. [Assign Storage Classes](/docs/operator/use-cases/storage-classes).
|
||||
@@ -29,4 +29,4 @@ Any attempt of Alice to use a disallowed `imagePullPolicies` value is denied by
|
||||
|
||||
# What’s next
|
||||
|
||||
See how Bill, the cluster admin, can assign trusted images registries to Alice's tenant. [Assign Trusted Images Registries](./images-registries.md).
|
||||
See how Bill, the cluster admin, can assign trusted images registries to Alice's tenant. [Assign Trusted Images Registries](/docs/operator/use-cases/images-registries).
|
||||
@@ -21,16 +21,14 @@ spec:
|
||||
allowedRegex: 'internal.registry.\\w.tld'
|
||||
```
|
||||
|
||||
> In case of `non-FQDI` (non fully qualified Docker image) and official images hosted on Docker Hub,
|
||||
> Capsule is going to retrieve the registry even if it's not explicit: a `busybox:latest` Pod
|
||||
> running on a Tenant allowing `docker.io` will not be blocked, even if the image
|
||||
> field is not explicit as `docker.io/busybox:latest`.
|
||||
> In case of Pod running `non-FQCI` (non fully qualified container image) containers, the container registry enforcement will disallow the execution.
|
||||
> If you would like to run a `busybox:latest` container that is commonly hosted on Docker Hub, the Tenant Owner has to specify its name explicitly, like `docker.io/library/busybox:latest`.
|
||||
|
||||
A Pod running `internal.registry.foo.tld` as registry will be allowed, as well `internal.registry.bar.tld` since these are matching the regular expression.
|
||||
A Pod running `internal.registry.foo.tld/capsule:latest` as registry will be allowed, as well `internal.registry.bar.tld` since these are matching the regular expression.
|
||||
|
||||
> A catch-all regex entry as `.*` allows every kind of registry, which would be the same result of unsetting `containerRegistries` at all.
|
||||
|
||||
Any attempt of Alice to use a not allowed `containerRegistries` value is denied by the Validation Webhook enforcing it.
|
||||
|
||||
# What’s next
|
||||
See how Bill, the cluster admin, can assign Pod Security Policies to Alice's tenant. [Assign Pod Security Policies](./pod-security-policies.md).
|
||||
See how Bill, the cluster admin, can assign Pod Security Policies to Alice's tenant. [Assign Pod Security Policies](/docs/operator/use-cases/pod-security-policies).
|
||||
@@ -49,4 +49,4 @@ EOF
|
||||
Any attempt of Alice to use a non-valid Ingress Class, or missing it, is denied by the Validation Webhook enforcing it.
|
||||
|
||||
# What’s next
|
||||
See how Bill, the cluster admin, can assign a set of dedicated ingress hostnames to Alice's tenant. [Assign Ingress Hostnames](./ingress-hostnames.md).
|
||||
See how Bill, the cluster admin, can assign a set of dedicated ingress hostnames to Alice's tenant. [Assign Ingress Hostnames](/docs/operator/use-cases/ingress-hostnames).
|
||||
@@ -50,4 +50,4 @@ EOF
|
||||
Any attempt of Alice to use a non-valid hostname is denied by the Validation Webhook enforcing it.
|
||||
|
||||
# What’s next
|
||||
See how Bill, the cluster admin, can control the hostname collision in Ingresses. [Control hostname collision in ingresses](./hostname-collision.md).
|
||||
See how Bill, the cluster admin, can control the hostname collision in Ingresses. [Control hostname collision in ingresses](/docs/operator/use-cases//hostname-collision).
|
||||
@@ -91,4 +91,4 @@ EOF
|
||||
|
||||
# What’s next
|
||||
|
||||
See how Bill, the cluster admin, can cordon all the Namespaces belonging to a Tenant. [Cordoning a Tenant](./cordoning-tenant.md).
|
||||
See how Bill, the cluster admin, can cordon all the Namespaces belonging to a Tenant. [Cordoning a Tenant](/docs/operator/use-cases/cordoning-tenant).
|
||||
@@ -1,4 +1,4 @@
|
||||
# Denying user-defined labels or annotations
|
||||
# Denying specific user-defined labels or annotations on Namespaces
|
||||
|
||||
By default, capsule allows tenant owners to add and modify any label or annotation on their namespaces.
|
||||
|
||||
@@ -13,9 +13,9 @@ kind: Tenant
|
||||
metadata:
|
||||
name: oil
|
||||
annotations:
|
||||
capsule.clastix.io/forbidden-namespace-labels: foo.acme.net, bar.acme.net
|
||||
capsule.clastix.io/forbidden-namespace-labels: foo.acme.net,bar.acme.net
|
||||
capsule.clastix.io/forbidden-namespace-labels-regexp: .*.acme.net
|
||||
capsule.clastix.io/forbidden-namespace-annotations: foo.acme.net, bar.acme.net
|
||||
capsule.clastix.io/forbidden-namespace-annotations: foo.acme.net,bar.acme.net
|
||||
capsule.clastix.io/forbidden-namespace-annotations-regexp: .*.acme.net
|
||||
spec:
|
||||
owners:
|
||||
@@ -25,6 +25,4 @@ EOF
|
||||
```
|
||||
|
||||
# What’s next
|
||||
This ends our tour in Capsule use cases. As we improve Capsule, more use cases about multi-tenancy, policy admission control, and cluster governance will be covered in the future.
|
||||
|
||||
Stay tuned!
|
||||
Let's check it out how to restore Tenants after a Velero Backup. [Velero Backup Restoration](/docs/operator/use-cases/velero-backup-restoration).
|
||||
@@ -99,4 +99,4 @@ kubectl -n oil-production delete networkpolicy production-network-policy
|
||||
Any attempt of Alice to delete the tenant network policy defined in the tenant manifest is denied by the Validation Webhook enforcing it.
|
||||
|
||||
# What’s next
|
||||
See how Bill can enforce the Pod containers image pull policy to `Always` to avoid leaking of private images when running on shared nodes. [Enforcing Pod containers image PullPolicy](./images-pullpolicy.md)
|
||||
See how Bill can enforce the Pod containers image pull policy to `Always` to avoid leaking of private images when running on shared nodes. [Enforcing Pod containers image PullPolicy](/docs/operator/use-cases/images-pullpolicy)
|
||||
@@ -0,0 +1,41 @@
|
||||
# Denying specific user-defined labels or annotations on Nodes
|
||||
|
||||
When using `capsule` together with [capsule-proxy](https://github.com/clastix/capsule-proxy), Bill can allow Tenant Owners to [modify Nodes](/docs/proxy/overview).
|
||||
|
||||
By default, it will allow tenant owners to add and modify any label or annotation on their nodes.
|
||||
|
||||
But there are some scenarios, when tenant owners should not have an ability to add or modify specific labels or annotations (there are some types of labels or annotations, which must be protected from modifications - for example, which are set by `cloud-providers` or `autoscalers`).
|
||||
|
||||
Bill, the cluster admin, can deny Tenant Owners to add or modify specific labels and annotations on Nodes:
|
||||
|
||||
```yaml
|
||||
kubectl apply -f - << EOF
|
||||
apiVersion: capsule.clastix.io/v1alpha1
|
||||
kind: CapsuleConfiguration
|
||||
metadata:
|
||||
name: default
|
||||
annotations:
|
||||
capsule.clastix.io/forbidden-node-labels: foo.acme.net,bar.acme.net
|
||||
capsule.clastix.io/forbidden-node-labels-regexp: .*.acme.net
|
||||
capsule.clastix.io/forbidden-node-annotations: foo.acme.net,bar.acme.net
|
||||
capsule.clastix.io/forbidden-node-annotations-regexp: .*.acme.net
|
||||
spec:
|
||||
userGroups:
|
||||
- capsule.clastix.io
|
||||
- system:serviceaccounts:default
|
||||
EOF
|
||||
```
|
||||
|
||||
> **Important note**
|
||||
>
|
||||
>Due to [CVE-2021-25735](https://github.com/kubernetes/kubernetes/issues/100096) this feature is only supported for Kubernetes version older than:
|
||||
>* v1.18.18
|
||||
>* v1.19.10
|
||||
>* v1.20.6
|
||||
>* v1.21.0
|
||||
|
||||
# What’s next
|
||||
|
||||
This ends our tour in Capsule use cases. As we improve Capsule, more use cases about multi-tenancy, policy admission control, and cluster governance will be covered in the future.
|
||||
|
||||
Stay tuned!
|
||||
@@ -61,4 +61,4 @@ no
|
||||
```
|
||||
|
||||
# What’s next
|
||||
See how Bill, the cluster admin, can assign an Ingress Class to Alice's tenant. [Assign Ingress Classes](./ingress-classes.md).
|
||||
See how Bill, the cluster admin, can assign an Ingress Class to Alice's tenant. [Assign Ingress Classes](/docs/operator/use-cases/ingress-classes).
|
||||
51
docs/content/operator/use-cases/overview.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Use cases for Capsule
|
||||
Using Capsule, a cluster admin can implement complex multi-tenant scenarios for both public and private deployments. Here is a list of common scenarios addressed by Capsule.
|
||||
|
||||
# Container as a Service (CaaS)
|
||||
***Acme Corp***, our sample organization, built a Container as a Service platform (CaaS), based on Kubernetes to serve multiple lines of business. Each line of business has its team of engineers that are responsible for the development, deployment, and operating of their digital products.
|
||||
|
||||
To simplify the usage of Capsule in this scenario, we'll work with the following actors:
|
||||
|
||||
* ***Bill***:
|
||||
he is the cluster administrator from the operations department of Acme Corp. and he is in charge of administration and maintains the CaaS platform.
|
||||
|
||||
* ***Alice***:
|
||||
she works as the IT Project Leader in the Oil & Gas Business Units. These are two new lines of business at Acme Corp. Alice is responsible for all the strategic IT projects in the two LOBs. She also is responsible for a team made of different job responsibilities (developers, administrators, SRE engineers, etc.) working in separate departments.
|
||||
|
||||
* ***Joe***:
|
||||
he works at Acme Corp, as a lead developer of a distributed team in Alice's organization. Joe is responsible for developing a mission-critical project in the Oil market.
|
||||
|
||||
* ***Bob***:
|
||||
he is the head of Engineering for the Water Business Unit, the main and historical line of business at Acme Corp. He is responsible for the development, deployment, and operation of multiple digital products in production for a large set of customers.
|
||||
|
||||
Use Capsule to address any of the following scenarios:
|
||||
|
||||
* [Assign Tenant Ownership](/docs/operator/use-cases/tenant-ownership)
|
||||
* [Create Namespaces](/docs/operator/use-cases/create-namespaces)
|
||||
* [Assign Permissions](/docs/operator/use-cases/permissions)
|
||||
* [Enforce Resources Quotas and Limits](/docs/operator/use-cases/resources-quota-limits)
|
||||
* [Enforce Pod Priority Classes](/docs/operator/use-cases/pod-priority-classes)
|
||||
* [Assign specific Node Pools](/docs/operator/use-cases/nodes-pool)
|
||||
* [Assign Ingress Classes](/docs/operator/use-cases/ingress-classes)
|
||||
* [Assign Ingress Hostnames](/docs/operator/use-cases/ingress-hostnames)
|
||||
* [Control hostname collision in Ingresses](/docs/operator/use-cases/hostname-collision)
|
||||
* [Assign Storage Classes](/docs/operator/use-cases/storage-classes)
|
||||
* [Assign Network Policies](/docs/operator/use-cases/network-policies)
|
||||
* [Enforce Containers image PullPolicy](/docs/operator/use-cases/images-pullpolicy)
|
||||
* [Assign Trusted Images Registries](/docs/operator/use-cases/images-registries)
|
||||
* [Assign Pod Security Policies](/docs/operator/use-cases/pod-security-policies)
|
||||
* [Create Custom Resources](/docs/operator/use-cases/custom-resources)
|
||||
* [Taint Namespaces](/docs/operator/use-cases/taint-namespaces)
|
||||
* [Assign multiple Tenants](/docs/operator/use-cases/multiple-tenants)
|
||||
* [Cordon Tenants](/docs/operator/use-cases/cordoning-tenant)
|
||||
* [Disable Service Types](/docs/operator/use-cases/service-type)
|
||||
* [Taint Services](/docs/operator/use-cases/taint-services)
|
||||
* [Allow adding labels and annotations on namespaces](/docs/operator/use-cases/namespace-labels-and-annotations)
|
||||
* [Velero Backup Restoration](/docs/operator/use-cases/velero-backup-restoration)
|
||||
* [Deny Wildcard Hostnames](/docs/operator/use-cases/deny-wildcard-hostnames)
|
||||
* [Denying specific user-defined labels or annotations on Nodes](/docs/operator/use-cases/node-labels-and-annotations)
|
||||
|
||||
> NB: as we improve Capsule, more use cases about multi-tenancy and cluster governance will be covered.
|
||||
|
||||
# What’s next
|
||||
Now let's see how the cluster admin onboards a new tenant. [Onboarding a new tenant](/docs/operator/use-cases/onboarding).
|
||||
@@ -51,4 +51,4 @@ no
|
||||
> Please, note the user `joe`, in the example above, is not acting as tenant owner. He can just operate in `oil-development` namespace as admin.
|
||||
|
||||
# What’s next
|
||||
See how Bill, the cluster admin, sets resources quota and limits for Alice's tenant. [Enforce resources quota and limits](./resources-quota-limits.md).
|
||||
See how Bill, the cluster admin, sets resources quota and limits for Alice's tenant. [Enforce resources quota and limits](/docs/operator/use-cases/resources-quota-limits).
|
||||
@@ -32,4 +32,4 @@ If a Pod is going to use a non-allowed _Priority Class_, it will be rejected by
|
||||
|
||||
# What’s next
|
||||
|
||||
See how Bill, the cluster admin, can assign a pool of nodes to Alice's tenant. [Assign a nodes pool](./nodes-pool.md).
|
||||
See how Bill, the cluster admin, can assign a pool of nodes to Alice's tenant. [Assign a nodes pool](/docs/operator/use-cases/nodes-pool).
|
||||
@@ -80,4 +80,4 @@ roleRef:
|
||||
With the above example, Capsule is forbidding any authenticated user in `oil-production` namespace to run privileged pods and to perform privilege escalation as declared by the Cluster Role `psp:privileged`.
|
||||
|
||||
# What’s next
|
||||
See how Bill, the cluster admin, can assign to Alice the permissions to create custom resources in her tenant. [Create Custom Resources](./custom-resources.md).
|
||||
See how Bill, the cluster admin, can assign to Alice the permissions to create custom resources in her tenant. [Create Custom Resources](/docs/operator/use-cases/custom-resources).
|
||||
@@ -249,4 +249,4 @@ no
|
||||
|
||||
# What’s next
|
||||
|
||||
See how Bill, the cluster admin, can enforce the PriorityClass of Pods running of Alice's tenant namespaces. [Enforce Pod Priority Classes](./pod-priority-classes.md)
|
||||
See how Bill, the cluster admin, can enforce the PriorityClass of Pods running of Alice's tenant namespaces. [Enforce Pod Priority Classes](/docs/operator/use-cases/pod-priority-classes)
|
||||
@@ -68,4 +68,4 @@ EOF
|
||||
With the above configuration, any attempt of Alice to create a Service of type `LoadBalancer` is denied by the Validation Webhook enforcing it. Default value is `true`.
|
||||
|
||||
# What’s next
|
||||
See how Bill, the cluster admin, can set taints on the Alice's services. [Taint services](./taint-services.md).
|
||||
See how Bill, the cluster admin, can set taints on the Alice's services. [Taint services](/docs/operator/use-cases/taint-services).
|
||||
@@ -41,4 +41,4 @@ EOF
|
||||
Any attempt of Alice to use a non-valid Storage Class, or missing it, is denied by the Validation Webhook enforcing it.
|
||||
|
||||
# What’s next
|
||||
See how Bill, the cluster admin, can assign Network Policies to Alice's tenant. [Assign Network Policies](./network-policies.md).
|
||||
See how Bill, the cluster admin, can assign Network Policies to Alice's tenant. [Assign Network Policies](/docs/operator/use-cases/network-policies).
|
||||
@@ -25,4 +25,4 @@ EOF
|
||||
When Alice creates a namespace, this will inherit the given label and/or annotation.
|
||||
|
||||
# What’s next
|
||||
See how Bill, the cluster admin, can assign multiple tenants to Alice. [Assign multiple tenants to an owner](./multiple-tenants.md).
|
||||
See how Bill, the cluster admin, can assign multiple tenants to Alice. [Assign multiple tenants to an owner](/docs/operator/use-cases/multiple-tenants).
|
||||
@@ -25,4 +25,4 @@ EOF
|
||||
When Alice creates a service in a namespace, this will inherit the given label and/or annotation.
|
||||
|
||||
# What’s next
|
||||
See how Bill, the cluster admin, can allow Alice to use specific labels or annotations. [Allow adding labels and annotations on namespaces](./namespace-labels-and-annotations.md).
|
||||
See how Bill, the cluster admin, can protect specific labels and annotations on Namespaces from modifications by Alice. [Denying specific user-defined labels or annotations on Namespaces](/docs/operator/use-cases/namespace-labels-and-annotations).
|
||||
@@ -1,4 +1,4 @@
|
||||
# Onboard a new tenant
|
||||
# Tenant ownership
|
||||
Bill, the cluster admin, receives a new request from Acme Corp.'s CTO asking for a new tenant to be onboarded and Alice user will be the tenant owner. Bill then assigns Alice's identity of `alice` in the Acme Corp. identity management system. Since Alice is a tenant owner, Bill needs to assign `alice` the Capsule group defined by `--capsule-user-group` option, which defaults to `capsule.clastix.io`.
|
||||
|
||||
To keep things simple, we assume that Bill just creates a client certificate for authentication using X.509 Certificate Signing Request, so Alice's certificate has `"/CN=alice/O=capsule.clastix.io"`.
|
||||
@@ -136,5 +136,26 @@ kubectl --as system:serviceaccount:default:robot --as-group capsule.clastix.io a
|
||||
yes
|
||||
```
|
||||
|
||||
The service account has to be part of Capsule group, so Bill has to set in the `CapsuleConfiguration`
|
||||
|
||||
```yaml
|
||||
apiVersion: capsule.clastix.io/v1alpha1
|
||||
kind: CapsuleConfiguration
|
||||
metadata:
|
||||
name: default
|
||||
spec:
|
||||
userGroups:
|
||||
- capsule.clastix.io
|
||||
- system:serviceaccounts:default
|
||||
```
|
||||
|
||||
because, by default, each service account is a member of following groups:
|
||||
|
||||
```
|
||||
system:serviceaccounts
|
||||
system:serviceaccounts:{service-account-namespace}
|
||||
system:authenticated
|
||||
```
|
||||
|
||||
# What’s next
|
||||
See how a tenant owner, creates new namespaces. [Create namespaces](./create-namespaces.md).
|
||||
See how a tenant owner, creates new namespaces. [Create namespaces](/docs/operator/use-cases/create-namespaces).
|
||||
@@ -20,4 +20,8 @@ Additionally, you can also specify a selected range of tenants to be restored:
|
||||
./velero-restore.sh --tenant "gas oil" restore
|
||||
```
|
||||
|
||||
In this way, only the tenants **gas** and **oil** will be restored.
|
||||
In this way, only the tenants **gas** and **oil** will be restored.
|
||||
|
||||
# What's next
|
||||
|
||||
See how Bill, the cluster admin, can deny wildcard hostnames to a Tenant. [Deny Wildcard Hostnames](/docs/operator/use-cases/deny-wildcard-hostnames)
|
||||
@@ -48,7 +48,7 @@ The `capsule-proxy` can be deployed in standalone mode, e.g. running as a pod br
|
||||
Optionally, it can be deployed as a sidecar container in the backend of a dashboard.
|
||||
Running outside a Kubernetes cluster is also viable, although a valid `KUBECONFIG` file must be provided, using the environment variable `KUBECONFIG` or the default file in `$HOME/.kube/config`.
|
||||
|
||||
An Helm Chart is available [here](./charts/capsule-proxy/README.md).
|
||||
An Helm Chart is available [here](https://github.com/clastix/capsule/blob/master/charts/capsule/README.md).
|
||||
|
||||
## Does it work with kubectl?
|
||||
|
||||
@@ -331,7 +331,7 @@ oil-production Active 2m
|
||||
# What’s next
|
||||
Have a fun with `capsule-proxy`:
|
||||
|
||||
* [Standalone Installation](./standalone.md)
|
||||
* [Sidecar Installation](./sidecar.md)
|
||||
* [OIDC Authentication](./oidc-auth.md)
|
||||
* [Contributing](./contributing.md)
|
||||
* [Standalone Installation](/docs/proxy/standalone)
|
||||
* [Sidecar Installation](/docs/proxy/sidecar)
|
||||
* [OIDC Authentication](/docs/proxy/oidc-auth)
|
||||
* [Contributing](/docs/proxy/contributing)
|
||||
@@ -112,5 +112,5 @@ data:
|
||||
|
||||
After starting the dashboard, login as a Tenant Owner user, e.g. `alice` according to the used authentication method, and check you can see only owned namespaces.
|
||||
|
||||
The `capsule-proxy` can be deployed in standalone mode, in order to be used with a command line tools like `kubectl`. See [Standalone Installation](./standalone.md).
|
||||
The `capsule-proxy` can be deployed in standalone mode, in order to be used with a command line tools like `kubectl`. See [Standalone Installation](/docs/proxy/standalone).
|
||||
|
||||