Compare commits
2 Commits
master
...
feature/be
| Author | SHA1 | Date | |
|---|---|---|---|
| 8fc9cb31c4 | |||
| 78b5af747e |
@@ -97,7 +97,8 @@ Katenary transforms compose services this way:
|
|||||||
- environment variables will be stored inside a `ConfigMap`
|
- environment variables will be stored inside a `ConfigMap`
|
||||||
- image, tags, and ingresses configuration are also stored in `values.yaml` file
|
- image, tags, and ingresses configuration are also stored in `values.yaml` file
|
||||||
- if named volumes are declared, Katenary create `PersistentVolumeClaims` - not enabled in values file
|
- if named volumes are declared, Katenary create `PersistentVolumeClaims` - not enabled in values file
|
||||||
- `depends_on` needs that the pointed service declared a port. If not, you can use labels to inform Katenary
|
- `depends_on` uses Kubernetes API by default to check if the service endpoint is ready. No port required.
|
||||||
|
Use label `katenary.v3/depends-on: legacy` to use the old netcat method (requires port).
|
||||||
|
|
||||||
For any other specific configuration, like binding local files as `ConfigMap`, bind variables, add values with
|
For any other specific configuration, like binding local files as `ConfigMap`, bind variables, add values with
|
||||||
documentation, etc. You'll need to use labels.
|
documentation, etc. You'll need to use labels.
|
||||||
@@ -147,10 +148,8 @@ Katenary proposes a lot of labels to configure the helm chart generation, but so
|
|||||||
|
|
||||||
### Work with Depends On?
|
### Work with Depends On?
|
||||||
|
|
||||||
Kubernetes does not provide service or pod starting detection from others pods. But Katenary will create `initContainer`
|
Katenary creates `initContainer` to wait for dependent services to be ready. By default, it uses the Kubernetes API
|
||||||
to make you able to wait for a service to respond. But you'll probably need to adapt a bit the compose file.
|
to check if the service endpoint has ready addresses - no port required.
|
||||||
|
|
||||||
See this compose file:
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: "3"
|
version: "3"
|
||||||
@@ -167,9 +166,7 @@ services:
|
|||||||
MYSQL_ROOT_PASSWORD: foobar
|
MYSQL_ROOT_PASSWORD: foobar
|
||||||
```
|
```
|
||||||
|
|
||||||
In this case, `webapp` needs to know the `database` port because the `depends_on` points on it and Kubernetes has not
|
If you need the old netcat-based method (requires port), add the `katenary.v3/depends-on: legacy` label to the dependent service:
|
||||||
(yet) solution to check the database startup. Katenary wants to create a `initContainer` to hit on the related service.
|
|
||||||
So, instead of exposing the port in the compose definition, let's declare this to Katenary with labels:
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: "3"
|
version: "3"
|
||||||
@@ -179,14 +176,15 @@ services:
|
|||||||
image: php:8-apache
|
image: php:8-apache
|
||||||
depends_on:
|
depends_on:
|
||||||
- database
|
- database
|
||||||
|
labels:
|
||||||
|
katenary.v3/depends-on: legacy
|
||||||
|
|
||||||
database:
|
database:
|
||||||
image: mariadb
|
image: mariadb
|
||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: foobar
|
MYSQL_ROOT_PASSWORD: foobar
|
||||||
labels:
|
ports:
|
||||||
katenary.v3/ports: |-
|
- 3306:3306
|
||||||
- 3306
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Declare ingresses
|
### Declare ingresses
|
||||||
|
|||||||
@@ -262,9 +262,21 @@ func (d *Deployment) BindFrom(service types.ServiceConfig, binded *Deployment) {
|
|||||||
|
|
||||||
// DependsOn adds a initContainer to the deployment that will wait for the service to be up.
|
// DependsOn adds a initContainer to the deployment that will wait for the service to be up.
|
||||||
func (d *Deployment) DependsOn(to *Deployment, servicename string) error {
|
func (d *Deployment) DependsOn(to *Deployment, servicename string) error {
|
||||||
// Add a initContainer with busybox:latest using netcat to check if the service is up
|
|
||||||
// it will wait until the service responds to all ports
|
|
||||||
logger.Info("Adding dependency from ", d.service.Name, " to ", to.service.Name)
|
logger.Info("Adding dependency from ", d.service.Name, " to ", to.service.Name)
|
||||||
|
|
||||||
|
useLegacy := false
|
||||||
|
if label, ok := d.service.Labels[labels.LabelDependsOn]; ok {
|
||||||
|
useLegacy = strings.ToLower(label) == "legacy"
|
||||||
|
}
|
||||||
|
|
||||||
|
if useLegacy {
|
||||||
|
return d.dependsOnLegacy(to, servicename)
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.dependsOnK8sAPI(to)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Deployment) dependsOnLegacy(to *Deployment, servicename string) error {
|
||||||
for _, container := range to.Spec.Template.Spec.Containers {
|
for _, container := range to.Spec.Template.Spec.Containers {
|
||||||
commands := []string{}
|
commands := []string{}
|
||||||
if len(container.Ports) == 0 {
|
if len(container.Ports) == 0 {
|
||||||
@@ -291,6 +303,39 @@ func (d *Deployment) DependsOn(to *Deployment, servicename string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Deployment) dependsOnK8sAPI(to *Deployment) error {
|
||||||
|
script := `NAMESPACE=${NAMESPACE:-default}
|
||||||
|
SERVICE=%s
|
||||||
|
KUBERNETES_SERVICE_HOST=${KUBERNETES_SERVICE_HOST:-kubernetes.default.svc}
|
||||||
|
KUBERNETES_SERVICE_PORT=${KUBERNETES_SERVICE_PORT:-443}
|
||||||
|
|
||||||
|
until wget -q -O- --header="Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
|
||||||
|
--cacert=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
|
||||||
|
"https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}/api/v1/namespaces/${NAMESPACE}/endpoints/${SERVICE}" \
|
||||||
|
| grep -q '"ready":.*true'; do
|
||||||
|
sleep 2
|
||||||
|
done`
|
||||||
|
|
||||||
|
command := []string{"/bin/sh", "-c", fmt.Sprintf(script, to.Name)}
|
||||||
|
d.Spec.Template.Spec.InitContainers = append(d.Spec.Template.Spec.InitContainers, corev1.Container{
|
||||||
|
Name: "wait-for-" + to.service.Name,
|
||||||
|
Image: "busybox:latest",
|
||||||
|
Command: command,
|
||||||
|
Env: []corev1.EnvVar{
|
||||||
|
{
|
||||||
|
Name: "NAMESPACE",
|
||||||
|
ValueFrom: &corev1.EnvVarSource{
|
||||||
|
FieldRef: &corev1.ObjectFieldSelector{
|
||||||
|
FieldPath: "metadata.namespace",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Filename returns the filename of the deployment.
|
// Filename returns the filename of the deployment.
|
||||||
func (d *Deployment) Filename() string {
|
func (d *Deployment) Filename() string {
|
||||||
return d.service.Name + ".deployment.yaml"
|
return d.service.Name + ".deployment.yaml"
|
||||||
|
|||||||
@@ -142,6 +142,86 @@ services:
|
|||||||
if len(dt.Spec.Template.Spec.InitContainers) != 1 {
|
if len(dt.Spec.Template.Spec.InitContainers) != 1 {
|
||||||
t.Errorf("Expected 1 init container, got %d", len(dt.Spec.Template.Spec.InitContainers))
|
t.Errorf("Expected 1 init container, got %d", len(dt.Spec.Template.Spec.InitContainers))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initContainer := dt.Spec.Template.Spec.InitContainers[0]
|
||||||
|
if !strings.Contains(initContainer.Image, "busybox") {
|
||||||
|
t.Errorf("Expected busybox image, got %s", initContainer.Image)
|
||||||
|
}
|
||||||
|
|
||||||
|
fullCommand := strings.Join(initContainer.Command, " ")
|
||||||
|
if !strings.Contains(fullCommand, "wget") {
|
||||||
|
t.Errorf("Expected wget command (K8s API method), got %s", fullCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(fullCommand, "/api/v1/namespaces/") {
|
||||||
|
t.Errorf("Expected Kubernetes API call to /api/v1/namespaces/, got %s", fullCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(fullCommand, "/endpoints/") {
|
||||||
|
t.Errorf("Expected Kubernetes API call to /endpoints/, got %s", fullCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(initContainer.Env) == 0 {
|
||||||
|
t.Errorf("Expected environment variables to be set for namespace")
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNamespace := false
|
||||||
|
for _, env := range initContainer.Env {
|
||||||
|
if env.Name == "NAMESPACE" && env.ValueFrom != nil && env.ValueFrom.FieldRef != nil {
|
||||||
|
if env.ValueFrom.FieldRef.FieldPath == "metadata.namespace" {
|
||||||
|
hasNamespace = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasNamespace {
|
||||||
|
t.Errorf("Expected NAMESPACE env var with metadata.namespace fieldRef")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDependsOnLegacy(t *testing.T) {
|
||||||
|
composeFile := `
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
image: nginx:1.29
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
|
depends_on:
|
||||||
|
- database
|
||||||
|
labels:
|
||||||
|
katenary.v3/depends-on: legacy
|
||||||
|
|
||||||
|
database:
|
||||||
|
image: mariadb:10.5
|
||||||
|
ports:
|
||||||
|
- 3306:3306
|
||||||
|
`
|
||||||
|
tmpDir := setup(composeFile)
|
||||||
|
defer teardown(tmpDir)
|
||||||
|
|
||||||
|
currentDir, _ := os.Getwd()
|
||||||
|
os.Chdir(tmpDir)
|
||||||
|
defer os.Chdir(currentDir)
|
||||||
|
|
||||||
|
output := internalCompileTest(t, "-s", webTemplateOutput)
|
||||||
|
dt := v1.Deployment{}
|
||||||
|
if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
|
||||||
|
t.Errorf(unmarshalError, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dt.Spec.Template.Spec.InitContainers) != 1 {
|
||||||
|
t.Errorf("Expected 1 init container, got %d", len(dt.Spec.Template.Spec.InitContainers))
|
||||||
|
}
|
||||||
|
|
||||||
|
initContainer := dt.Spec.Template.Spec.InitContainers[0]
|
||||||
|
if !strings.Contains(initContainer.Image, "busybox") {
|
||||||
|
t.Errorf("Expected busybox image, got %s", initContainer.Image)
|
||||||
|
}
|
||||||
|
|
||||||
|
fullCommand := strings.Join(initContainer.Command, " ")
|
||||||
|
if !strings.Contains(fullCommand, "nc") {
|
||||||
|
t.Errorf("Expected nc (netcat) command for legacy method, got %s", fullCommand)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHelmDependencies(t *testing.T) {
|
func TestHelmDependencies(t *testing.T) {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ const (
|
|||||||
LabelEnvFrom Label = KatenaryLabelPrefix + "/env-from"
|
LabelEnvFrom Label = KatenaryLabelPrefix + "/env-from"
|
||||||
LabelExchangeVolume Label = KatenaryLabelPrefix + "/exchange-volumes"
|
LabelExchangeVolume Label = KatenaryLabelPrefix + "/exchange-volumes"
|
||||||
LabelValuesFrom Label = KatenaryLabelPrefix + "/values-from"
|
LabelValuesFrom Label = KatenaryLabelPrefix + "/values-from"
|
||||||
|
LabelDependsOn Label = KatenaryLabelPrefix + "/depends-on"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -355,4 +355,25 @@
|
|||||||
DB_USER: database.MARIADB_USER
|
DB_USER: database.MARIADB_USER
|
||||||
DB_PASSWORD: database.MARIADB_PASSWORD
|
DB_PASSWORD: database.MARIADB_PASSWORD
|
||||||
|
|
||||||
|
"depends-on":
|
||||||
|
short: "Method to check if a service is ready (for depends_on)."
|
||||||
|
long: |-
|
||||||
|
When a service uses `depends_on`, Katenary creates an initContainer to wait
|
||||||
|
for the dependent service to be ready.
|
||||||
|
|
||||||
|
By default, Katenary uses the Kubernetes API to check if the service endpoint
|
||||||
|
has ready addresses. This method does not require the service to expose a port.
|
||||||
|
|
||||||
|
Set this label to `legacy` to use the old netcat method that requires a port
|
||||||
|
to be defined for the dependent service.
|
||||||
|
example: |-
|
||||||
|
web:
|
||||||
|
image: nginx
|
||||||
|
depends_on:
|
||||||
|
- database
|
||||||
|
labels:
|
||||||
|
# Use legacy netcat method (requires port)
|
||||||
|
{{ .KatenaryPrefix }}/depends-on: legacy
|
||||||
|
type: "string"
|
||||||
|
|
||||||
# vim: ft=gotmpl.yaml
|
# vim: ft=gotmpl.yaml
|
||||||
|
|||||||
Reference in New Issue
Block a user