feat(volume): add "exchange volumes"
This volumes are "emptyDir" and can have init command. For example, in a "same-pod", it allow the user to copy data from image to a directory that is mounted on others pods.
This commit is contained in:
@@ -209,10 +209,21 @@ func (chart *HelmChart) generateDeployment(service types.ServiceConfig, deployme
|
|||||||
// generate the cronjob if needed
|
// generate the cronjob if needed
|
||||||
chart.setCronJob(service, appName)
|
chart.setCronJob(service, appName)
|
||||||
|
|
||||||
|
if exchange, ok := service.Labels[labels.LabelExchangeVolume]; ok {
|
||||||
|
// we need to add a volume and a mount point
|
||||||
|
ex, err := labelStructs.NewExchangeVolumes(exchange)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, exchangeVolume := range ex {
|
||||||
|
d.AddLegacyVolume("exchange-"+exchangeVolume.Name, exchangeVolume.Type)
|
||||||
|
d.exchangesVolumes[service.Name] = exchangeVolume
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// get the same-pod label if exists, add it to the list.
|
// get the same-pod label if exists, add it to the list.
|
||||||
// We later will copy some parts to the target deployment and remove this one.
|
// We later will copy some parts to the target deployment and remove this one.
|
||||||
if samePod, ok := service.Labels[labels.LabelSamePod]; ok && samePod != "" {
|
if samePod, ok := service.Labels[labels.LabelSamePod]; ok && samePod != "" {
|
||||||
log.Printf("Found same-pod label for %s", service.Name)
|
|
||||||
podToMerge[samePod] = &service
|
podToMerge[samePod] = &service
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -39,6 +39,7 @@ type Deployment struct {
|
|||||||
service *types.ServiceConfig `yaml:"-"`
|
service *types.ServiceConfig `yaml:"-"`
|
||||||
defaultTag string `yaml:"-"`
|
defaultTag string `yaml:"-"`
|
||||||
isMainApp bool `yaml:"-"`
|
isMainApp bool `yaml:"-"`
|
||||||
|
exchangesVolumes map[string]*labelStructs.ExchangeVolume `yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDeployment creates a new Deployment from a compose service. The appName is the name of the application taken from the project name.
|
// NewDeployment creates a new Deployment from a compose service. The appName is the name of the application taken from the project name.
|
||||||
@@ -92,6 +93,7 @@ func NewDeployment(service types.ServiceConfig, chart *HelmChart) *Deployment {
|
|||||||
},
|
},
|
||||||
configMaps: make(map[string]*ConfigMapMount),
|
configMaps: make(map[string]*ConfigMapMount),
|
||||||
volumeMap: make(map[string]string),
|
volumeMap: make(map[string]string),
|
||||||
|
exchangesVolumes: map[string]*labelStructs.ExchangeVolume{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// add containers
|
// add containers
|
||||||
@@ -212,6 +214,27 @@ func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Deployment) AddLegacyVolume(name, kind string) {
|
||||||
|
// ensure the volume is not present
|
||||||
|
for _, v := range d.Spec.Template.Spec.Volumes {
|
||||||
|
if v.Name == name {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
if d.Spec.Template.Spec.Volumes == nil {
|
||||||
|
d.Spec.Template.Spec.Volumes = []corev1.Volume{}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Spec.Template.Spec.Volumes = append(d.Spec.Template.Spec.Volumes, corev1.Volume{
|
||||||
|
Name: name,
|
||||||
|
VolumeSource: corev1.VolumeSource{
|
||||||
|
EmptyDir: &corev1.EmptyDirVolumeSource{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Deployment) BindFrom(service types.ServiceConfig, binded *Deployment) {
|
func (d *Deployment) BindFrom(service types.ServiceConfig, binded *Deployment) {
|
||||||
// find the volume in the binded deployment
|
// find the volume in the binded deployment
|
||||||
for _, bindedVolume := range binded.Spec.Template.Spec.Volumes {
|
for _, bindedVolume := range binded.Spec.Template.Spec.Volumes {
|
||||||
@@ -276,14 +299,27 @@ func (d *Deployment) Filename() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetEnvFrom sets the environment variables to a configmap. The configmap is created.
|
// SetEnvFrom sets the environment variables to a configmap. The configmap is created.
|
||||||
func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string) {
|
func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string, samePod ...bool) {
|
||||||
if len(service.Environment) == 0 {
|
if len(service.Environment) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
inSamePod := false
|
||||||
|
if len(samePod) > 0 && samePod[0] {
|
||||||
|
inSamePod = true
|
||||||
|
}
|
||||||
|
|
||||||
drop := []string{}
|
drop := []string{}
|
||||||
secrets := []string{}
|
secrets := []string{}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
c, index := d.BindMapFilesToContainer(service, secrets, appName)
|
||||||
|
if c == nil || index == -1 {
|
||||||
|
log.Println("Container not found for service ", service.Name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.Spec.Template.Spec.Containers[index] = *c
|
||||||
|
}()
|
||||||
|
|
||||||
// secrets from label
|
// secrets from label
|
||||||
labelSecrets, err := labelStructs.SecretsFrom(service.Labels[labels.LabelSecrets])
|
labelSecrets, err := labelStructs.SecretsFrom(service.Labels[labels.LabelSecrets])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -308,6 +344,10 @@ func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string) {
|
|||||||
secrets = append(secrets, secret)
|
secrets = append(secrets, secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if inSamePod {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// for each values from label "values", add it to Values map and change the envFrom
|
// for each values from label "values", add it to Values map and change the envFrom
|
||||||
// value to {{ .Values.<service>.<value> }}
|
// value to {{ .Values.<service>.<value> }}
|
||||||
for _, value := range labelValues {
|
for _, value := range labelValues {
|
||||||
@@ -330,10 +370,26 @@ func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string) {
|
|||||||
for _, value := range drop {
|
for _, value := range drop {
|
||||||
delete(service.Environment, value)
|
delete(service.Environment, value)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Deployment) BindMapFilesToContainer(service types.ServiceConfig, secrets []string, appName string) (*corev1.Container, int) {
|
||||||
fromSources := []corev1.EnvFromSource{}
|
fromSources := []corev1.EnvFromSource{}
|
||||||
|
|
||||||
if len(service.Environment) > 0 {
|
envSize := len(service.Environment)
|
||||||
|
|
||||||
|
for _, secret := range secrets {
|
||||||
|
for k := range service.Environment {
|
||||||
|
if k == secret {
|
||||||
|
envSize--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if envSize > 0 {
|
||||||
|
if service.Name == "db" {
|
||||||
|
log.Println("Service ", service.Name, " has environment variables")
|
||||||
|
log.Println(service.Environment)
|
||||||
|
}
|
||||||
fromSources = append(fromSources, corev1.EnvFromSource{
|
fromSources = append(fromSources, corev1.EnvFromSource{
|
||||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||||
LocalObjectReference: corev1.LocalObjectReference{
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
@@ -356,7 +412,7 @@ func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string) {
|
|||||||
container, index := utils.GetContainerByName(service.Name, d.Spec.Template.Spec.Containers)
|
container, index := utils.GetContainerByName(service.Name, d.Spec.Template.Spec.Containers)
|
||||||
if container == nil {
|
if container == nil {
|
||||||
utils.Warn("Container not found for service " + service.Name)
|
utils.Warn("Container not found for service " + service.Name)
|
||||||
return
|
return nil, -1
|
||||||
}
|
}
|
||||||
|
|
||||||
container.EnvFrom = append(container.EnvFrom, fromSources...)
|
container.EnvFrom = append(container.EnvFrom, fromSources...)
|
||||||
@@ -364,8 +420,30 @@ func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string) {
|
|||||||
if container.Env == nil {
|
if container.Env == nil {
|
||||||
container.Env = []corev1.EnvVar{}
|
container.Env = []corev1.EnvVar{}
|
||||||
}
|
}
|
||||||
|
return container, index
|
||||||
|
}
|
||||||
|
|
||||||
d.Spec.Template.Spec.Containers[index] = *container
|
func (d *Deployment) MountExchangeVolumes() {
|
||||||
|
for name, ex := range d.exchangesVolumes {
|
||||||
|
for i, c := range d.Spec.Template.Spec.Containers {
|
||||||
|
c.VolumeMounts = append(c.VolumeMounts, corev1.VolumeMount{
|
||||||
|
Name: "exchange-" + ex.Name,
|
||||||
|
MountPath: ex.MountPath,
|
||||||
|
})
|
||||||
|
if len(ex.Init) > 0 && name == c.Name {
|
||||||
|
d.Spec.Template.Spec.InitContainers = append(d.Spec.Template.Spec.InitContainers, corev1.Container{
|
||||||
|
Command: []string{"/bin/sh", "-c", ex.Init},
|
||||||
|
Image: c.Image,
|
||||||
|
Name: "exhange-init-" + name,
|
||||||
|
VolumeMounts: []corev1.VolumeMount{{
|
||||||
|
Name: "exchange-" + ex.Name,
|
||||||
|
MountPath: ex.MountPath,
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
d.Spec.Template.Spec.Containers[i] = c
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Yaml returns the yaml representation of the deployment.
|
// Yaml returns the yaml representation of the deployment.
|
||||||
|
@@ -79,6 +79,11 @@ func Generate(project *types.Project) (*HelmChart, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if we have built exchange volumes, we need to moint them in each deployment
|
||||||
|
for _, d := range deployments {
|
||||||
|
d.MountExchangeVolumes()
|
||||||
|
}
|
||||||
|
|
||||||
// drop all "same-pod" deployments because the containers and volumes are already
|
// drop all "same-pod" deployments because the containers and volumes are already
|
||||||
// in the target deployment
|
// in the target deployment
|
||||||
for _, service := range podToMerge {
|
for _, service := range podToMerge {
|
||||||
@@ -87,7 +92,10 @@ func Generate(project *types.Project) (*HelmChart, error) {
|
|||||||
if target, ok := deployments[samepod]; ok {
|
if target, ok := deployments[samepod]; ok {
|
||||||
target.AddContainer(*service)
|
target.AddContainer(*service)
|
||||||
target.BindFrom(*service, deployments[service.Name])
|
target.BindFrom(*service, deployments[service.Name])
|
||||||
target.SetEnvFrom(*service, appName)
|
target.SetEnvFrom(*service, appName, true)
|
||||||
|
// copy all init containers
|
||||||
|
initContainers := deployments[service.Name].Spec.Template.Spec.InitContainers
|
||||||
|
target.Spec.Template.Spec.InitContainers = append(target.Spec.Template.Spec.InitContainers, initContainers...)
|
||||||
delete(deployments, service.Name)
|
delete(deployments, service.Name)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("service %[1]s is declared as %[2]s, but %[2]s is not defined", service.Name, labels.LabelSamePod)
|
log.Printf("service %[1]s is declared as %[2]s, but %[2]s is not defined", service.Name, labels.LabelSamePod)
|
||||||
|
@@ -39,6 +39,7 @@ type Service struct {
|
|||||||
MapEnv *labelStructs.MapEnv `json:"map-env,omitempty" jsonschema:"title=Map Env,description=Map environment variables to another value"`
|
MapEnv *labelStructs.MapEnv `json:"map-env,omitempty" jsonschema:"title=Map Env,description=Map environment variables to another value"`
|
||||||
CronJob *labelStructs.CronJob `json:"cron-job,omitempty" jsonschema:"title=Cron Job,description=Cron Job configuration"`
|
CronJob *labelStructs.CronJob `json:"cron-job,omitempty" jsonschema:"title=Cron Job,description=Cron Job configuration"`
|
||||||
EnvFrom *labelStructs.EnvFrom `json:"env-from,omitempty" jsonschema:"title=Env From,description=Inject environment variables from another service"`
|
EnvFrom *labelStructs.EnvFrom `json:"env-from,omitempty" jsonschema:"title=Env From,description=Inject environment variables from another service"`
|
||||||
|
ExchangeVolumes []*labelStructs.ExchangeVolume `json:"exchange-volumes,omitempty" jsonschema:"title=Exchange Volumes,description=Exchange volumes between services"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OverrideWithConfig overrides the project with the katenary.yaml file. It
|
// OverrideWithConfig overrides the project with the katenary.yaml file. It
|
||||||
@@ -89,6 +90,7 @@ func OverrideWithConfig(project *types.Project) {
|
|||||||
getLabelContent(s.MapEnv, &project.Services[i], labels.LabelMapEnv)
|
getLabelContent(s.MapEnv, &project.Services[i], labels.LabelMapEnv)
|
||||||
getLabelContent(s.CronJob, &project.Services[i], labels.LabelCronJob)
|
getLabelContent(s.CronJob, &project.Services[i], labels.LabelCronJob)
|
||||||
getLabelContent(s.EnvFrom, &project.Services[i], labels.LabelEnvFrom)
|
getLabelContent(s.EnvFrom, &project.Services[i], labels.LabelEnvFrom)
|
||||||
|
getLabelContent(s.ExchangeVolumes, &project.Services[i], labels.LabelExchangeVolume)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Println(utils.IconInfo, "Katenary file loaded successfully, the services are now configured.")
|
fmt.Println(utils.IconInfo, "Katenary file loaded successfully, the services are now configured.")
|
||||||
|
@@ -32,6 +32,7 @@ const (
|
|||||||
LabelConfigMapFiles Label = KatenaryLabelPrefix + "/configmap-files"
|
LabelConfigMapFiles Label = KatenaryLabelPrefix + "/configmap-files"
|
||||||
LabelCronJob Label = KatenaryLabelPrefix + "/cronjob"
|
LabelCronJob Label = KatenaryLabelPrefix + "/cronjob"
|
||||||
LabelEnvFrom Label = KatenaryLabelPrefix + "/env-from"
|
LabelEnvFrom Label = KatenaryLabelPrefix + "/env-from"
|
||||||
|
LabelExchangeVolume Label = KatenaryLabelPrefix + "/exchange-volumes"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@@ -284,4 +284,40 @@
|
|||||||
{{ .KatenaryPrefix }}/env-from: |-
|
{{ .KatenaryPrefix }}/env-from: |-
|
||||||
- myservice1
|
- myservice1
|
||||||
|
|
||||||
|
"exchange-volumes":
|
||||||
|
short: Add exchange volumes (empty directory on the node) to share data
|
||||||
|
type: "list of objects"
|
||||||
|
long: |-
|
||||||
|
This label allows sharing data between containres. The volume is created in
|
||||||
|
the node and mounted in the pod. It is useful to share data between containers
|
||||||
|
in a "same pod" logic. For example to let PHP-FPM and Nginx share the same direcotory.
|
||||||
|
|
||||||
|
This will create:
|
||||||
|
- an `emptyDir` volume in the deployment
|
||||||
|
- a `voumeMount` in the pod for **each container**
|
||||||
|
- a `initContainer` for each definition
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
- name: the name of the volume (manadatory)
|
||||||
|
- mountPath: the path where the volume is mounted in the pod (optional, default is `/opt`)
|
||||||
|
- init: a command to run to initialize the volume with data (optional)
|
||||||
|
|
||||||
|
!!! Warning
|
||||||
|
This is highly experimental. This is mainly useful when using the "same-pod" label.
|
||||||
|
|
||||||
|
example: |-
|
||||||
|
nginx:
|
||||||
|
# ...
|
||||||
|
labels;
|
||||||
|
{{ .KatenaryPrefix }}/exchange-volumes: |-
|
||||||
|
- name: php-fpm
|
||||||
|
mountPath: /var/www/html
|
||||||
|
php:
|
||||||
|
# ...
|
||||||
|
labels:
|
||||||
|
{{ .KatenaryPrefix }}/exchange-volumes: |-
|
||||||
|
- name: php-fpm
|
||||||
|
mountPath: /opt
|
||||||
|
init: cp -ra /var/www/html/* /opt
|
||||||
|
|
||||||
# vim: ft=gotmpl.yaml
|
# vim: ft=gotmpl.yaml
|
||||||
|
20
generator/labels/labelStructs/exchangeVolume.go
Normal file
20
generator/labels/labelStructs/exchangeVolume.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package labelStructs
|
||||||
|
|
||||||
|
import "gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
type ExchangeVolume struct {
|
||||||
|
Name string `yaml:"name" json:"name"`
|
||||||
|
MountPath string `yaml:"mountPath" json:"mountPath"`
|
||||||
|
Type string `yaml:"type,omitempty" json:"type,omitempty"`
|
||||||
|
Init string `yaml:"init,omitempty" json:"init,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExchangeVolumes(data string) ([]*ExchangeVolume, error) {
|
||||||
|
mapping := []*ExchangeVolume{}
|
||||||
|
|
||||||
|
if err := yaml.Unmarshal([]byte(data), &mapping); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapping, nil
|
||||||
|
}
|
Reference in New Issue
Block a user