github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/component/lorry_utils.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package component
    21  
    22  import (
    23  	"encoding/json"
    24  	"fmt"
    25  	"strconv"
    26  
    27  	corev1 "k8s.io/api/core/v1"
    28  	"k8s.io/apimachinery/pkg/util/intstr"
    29  
    30  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    31  	"github.com/1aal/kubeblocks/pkg/constant"
    32  	"github.com/1aal/kubeblocks/pkg/controller/builder"
    33  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    34  	viper "github.com/1aal/kubeblocks/pkg/viperx"
    35  )
    36  
    37  const (
    38  	// http://localhost:<port>/v1.0/bindings/<binding_type>
    39  	// checkRoleURIFormat        = "/v1.0/bindings/%s?operation=checkRole&workloadType=%s"
    40  	checkRoleURIFormat        = "/v1.0/checkrole"
    41  	checkRunningURIFormat     = "/v1.0/bindings/%s?operation=checkRunning"
    42  	checkStatusURIFormat      = "/v1.0/bindings/%s?operation=checkStatus"
    43  	volumeProtectionURIFormat = "/v1.0/bindings/%s?operation=volumeProtection"
    44  
    45  	dataVolume = "data"
    46  )
    47  
    48  var (
    49  	// default probe setting for volume protection.
    50  	defaultVolumeProtectionProbe = appsv1alpha1.ClusterDefinitionProbe{
    51  		PeriodSeconds:    60,
    52  		TimeoutSeconds:   5,
    53  		FailureThreshold: 3,
    54  	}
    55  )
    56  
    57  func buildLorryContainers(reqCtx intctrlutil.RequestCtx, component *SynthesizedComponent) error {
    58  	container := buildBasicContainer()
    59  	var lorryContainers []corev1.Container
    60  	componentProbes := component.Probes
    61  	if componentProbes == nil {
    62  		return nil
    63  	}
    64  	reqCtx.Log.V(3).Info("lorry", "settings", componentProbes)
    65  	lorrySvcHTTPPort := viper.GetInt32("PROBE_SERVICE_HTTP_PORT")
    66  	// override by new env name
    67  	if viper.IsSet("LORRY_SERVICE_HTTP_PORT") {
    68  		lorrySvcHTTPPort = viper.GetInt32("LORRY_SERVICE_HTTP_PORT")
    69  	}
    70  	availablePorts, err := getAvailableContainerPorts(component.PodSpec.Containers, []int32{lorrySvcHTTPPort})
    71  	lorrySvcHTTPPort = availablePorts[0]
    72  	if err != nil {
    73  		reqCtx.Log.Info("get lorry container port failed", "error", err)
    74  		return err
    75  	}
    76  	lorrySvcGRPCPort := viper.GetInt("PROBE_SERVICE_GRPC_PORT")
    77  
    78  	if componentProbes.RoleProbe != nil && (component.RSMSpec == nil || component.RSMSpec.RoleProbe == nil) {
    79  		roleChangedContainer := container.DeepCopy()
    80  		buildRoleProbeContainer(component, roleChangedContainer, componentProbes.RoleProbe, int(lorrySvcHTTPPort))
    81  		lorryContainers = append(lorryContainers, *roleChangedContainer)
    82  	}
    83  
    84  	if componentProbes.StatusProbe != nil {
    85  		statusProbeContainer := container.DeepCopy()
    86  		buildStatusProbeContainer(component.CharacterType, statusProbeContainer, componentProbes.StatusProbe, int(lorrySvcHTTPPort))
    87  		lorryContainers = append(lorryContainers, *statusProbeContainer)
    88  	}
    89  
    90  	if componentProbes.RunningProbe != nil {
    91  		runningProbeContainer := container.DeepCopy()
    92  		buildRunningProbeContainer(component.CharacterType, runningProbeContainer, componentProbes.RunningProbe, int(lorrySvcHTTPPort))
    93  		lorryContainers = append(lorryContainers, *runningProbeContainer)
    94  	}
    95  
    96  	if volumeProtectionEnabled(component) {
    97  		c := container.DeepCopy()
    98  		buildVolumeProtectionProbeContainer(component.CharacterType, c, int(lorrySvcHTTPPort))
    99  		lorryContainers = append(lorryContainers, *c)
   100  	}
   101  
   102  	// inject WeSyncer(currently part of Lorry) in cluster controller.
   103  	// as all the above features share the lorry service, only one lorry need to be injected.
   104  	// if none of the above feature enabled, WeSyncer still need to be injected for the HA feature functions well.
   105  	if len(lorryContainers) == 0 {
   106  		weSyncerContainer := container.DeepCopy()
   107  		buildWeSyncerContainer(weSyncerContainer, int(lorrySvcHTTPPort))
   108  		lorryContainers = append(lorryContainers, *weSyncerContainer)
   109  	}
   110  
   111  	buildLorryServiceContainer(component, &lorryContainers[0], int(lorrySvcHTTPPort), lorrySvcGRPCPort)
   112  
   113  	reqCtx.Log.V(1).Info("lorry", "containers", lorryContainers)
   114  	component.PodSpec.Containers = append(component.PodSpec.Containers, lorryContainers...)
   115  	return nil
   116  }
   117  
   118  func buildBasicContainer() *corev1.Container {
   119  	return builder.NewContainerBuilder("string").
   120  		SetImage("infracreate-registry.cn-zhangjiakou.cr.aliyuncs.com/google_containers/pause:3.6").
   121  		SetImagePullPolicy(corev1.PullIfNotPresent).
   122  		AddCommands("/pause").
   123  		AddEnv(corev1.EnvVar{
   124  			Name: "KB_SERVICE_USER",
   125  			ValueFrom: &corev1.EnvVarSource{
   126  				SecretKeyRef: &corev1.SecretKeySelector{
   127  					Key:                  "username",
   128  					LocalObjectReference: corev1.LocalObjectReference{Name: "$(CONN_CREDENTIAL_SECRET_NAME)"},
   129  				},
   130  			}},
   131  			corev1.EnvVar{
   132  				Name: "KB_SERVICE_PASSWORD",
   133  				ValueFrom: &corev1.EnvVarSource{
   134  					SecretKeyRef: &corev1.SecretKeySelector{
   135  						Key:                  "password",
   136  						LocalObjectReference: corev1.LocalObjectReference{Name: "$(CONN_CREDENTIAL_SECRET_NAME)"},
   137  					},
   138  				},
   139  			}).
   140  		SetStartupProbe(corev1.Probe{
   141  			ProbeHandler: corev1.ProbeHandler{
   142  				TCPSocket: &corev1.TCPSocketAction{Port: intstr.FromInt(3501)},
   143  			}}).
   144  		GetObject()
   145  }
   146  
   147  func buildLorryServiceContainer(component *SynthesizedComponent, container *corev1.Container, lorrySvcHTTPPort, lorrySvcGRPCPort int) {
   148  	container.Image = viper.GetString(constant.KBToolsImage)
   149  	container.ImagePullPolicy = corev1.PullPolicy(viper.GetString(constant.KBImagePullPolicy))
   150  	container.Command = []string{"lorry",
   151  		"--port", strconv.Itoa(lorrySvcHTTPPort),
   152  		"--config-path", "/config/lorry/components/",
   153  		"--grpcport", strconv.Itoa(lorrySvcGRPCPort),
   154  	}
   155  
   156  	if len(component.PodSpec.Containers) > 0 {
   157  		mainContainer := component.PodSpec.Containers[0]
   158  		if len(mainContainer.Ports) > 0 {
   159  			port := mainContainer.Ports[0]
   160  			dbPort := port.ContainerPort
   161  			container.Env = append(container.Env, corev1.EnvVar{
   162  				Name:      constant.KBEnvServicePort,
   163  				Value:     strconv.Itoa(int(dbPort)),
   164  				ValueFrom: nil,
   165  			})
   166  		}
   167  
   168  		dataVolumeName := dataVolume
   169  		for _, v := range component.VolumeTypes {
   170  			if v.Type == appsv1alpha1.VolumeTypeData {
   171  				dataVolumeName = v.Name
   172  			}
   173  		}
   174  		for _, volumeMount := range mainContainer.VolumeMounts {
   175  			if volumeMount.Name != dataVolumeName {
   176  				continue
   177  			}
   178  			vm := volumeMount.DeepCopy()
   179  			container.VolumeMounts = []corev1.VolumeMount{*vm}
   180  			container.Env = append(container.Env, corev1.EnvVar{
   181  				Name:      constant.KBEnvDataPath,
   182  				Value:     vm.MountPath,
   183  				ValueFrom: nil,
   184  			})
   185  		}
   186  	}
   187  
   188  	secretName := fmt.Sprintf("%s-conn-credential", component.ClusterName)
   189  	container.Env = append(container.Env,
   190  		corev1.EnvVar{
   191  			Name:      constant.KBEnvCharacterType,
   192  			Value:     component.CharacterType,
   193  			ValueFrom: nil,
   194  		},
   195  		corev1.EnvVar{
   196  			Name:      constant.KBEnvWorkloadType,
   197  			Value:     string(component.WorkloadType),
   198  			ValueFrom: nil,
   199  		},
   200  		corev1.EnvVar{
   201  			Name: constant.KBEnvServiceUser,
   202  			ValueFrom: &corev1.EnvVarSource{
   203  				SecretKeyRef: &corev1.SecretKeySelector{
   204  					LocalObjectReference: corev1.LocalObjectReference{
   205  						Name: secretName,
   206  					},
   207  					Key: constant.AccountNameForSecret,
   208  				},
   209  			},
   210  		},
   211  		corev1.EnvVar{
   212  			Name: constant.KBEnvServicePassword,
   213  			ValueFrom: &corev1.EnvVarSource{
   214  				SecretKeyRef: &corev1.SecretKeySelector{
   215  					LocalObjectReference: corev1.LocalObjectReference{
   216  						Name: secretName,
   217  					},
   218  					Key: constant.AccountPasswdForSecret,
   219  				},
   220  			},
   221  		})
   222  
   223  	container.Ports = []corev1.ContainerPort{
   224  		{
   225  			ContainerPort: int32(lorrySvcHTTPPort),
   226  			Name:          constant.LorryHTTPPortName,
   227  			Protocol:      "TCP",
   228  		},
   229  		{
   230  			ContainerPort: int32(lorrySvcGRPCPort),
   231  			Name:          constant.LorryGRPCPortName,
   232  			Protocol:      "TCP",
   233  		},
   234  	}
   235  
   236  	// pass the volume protection spec to lorry container through env.
   237  	if volumeProtectionEnabled(component) {
   238  		container.Env = append(container.Env, env4VolumeProtection(*component.VolumeProtection))
   239  	}
   240  }
   241  
   242  func buildWeSyncerContainer(weSyncerContainer *corev1.Container, probeSvcHTTPPort int) {
   243  	weSyncerContainer.Name = constant.WeSyncerContainerName
   244  	weSyncerContainer.StartupProbe.TCPSocket.Port = intstr.FromInt(probeSvcHTTPPort)
   245  }
   246  
   247  func buildRoleProbeContainer(component *SynthesizedComponent, roleChangedContainer *corev1.Container,
   248  	probeSetting *appsv1alpha1.ClusterDefinitionProbe, probeSvcHTTPPort int) {
   249  	roleChangedContainer.Name = constant.RoleProbeContainerName
   250  	httpGet := &corev1.HTTPGetAction{}
   251  	httpGet.Path = checkRoleURIFormat
   252  	httpGet.Port = intstr.FromInt(probeSvcHTTPPort)
   253  	probe := &corev1.Probe{}
   254  	probe.Exec = nil
   255  	probe.HTTPGet = httpGet
   256  	probe.PeriodSeconds = probeSetting.PeriodSeconds
   257  	probe.TimeoutSeconds = probeSetting.TimeoutSeconds
   258  	probe.FailureThreshold = probeSetting.FailureThreshold
   259  	roleChangedContainer.ReadinessProbe = probe
   260  	roleChangedContainer.StartupProbe.TCPSocket.Port = intstr.FromInt(probeSvcHTTPPort)
   261  }
   262  
   263  func buildStatusProbeContainer(characterType string, statusProbeContainer *corev1.Container,
   264  	probeSetting *appsv1alpha1.ClusterDefinitionProbe, probeSvcHTTPPort int) {
   265  	statusProbeContainer.Name = constant.StatusProbeContainerName
   266  	probe := &corev1.Probe{}
   267  	httpGet := &corev1.HTTPGetAction{}
   268  	httpGet.Path = fmt.Sprintf(checkStatusURIFormat, characterType)
   269  	httpGet.Port = intstr.FromInt(probeSvcHTTPPort)
   270  	probe.HTTPGet = httpGet
   271  	probe.PeriodSeconds = probeSetting.PeriodSeconds
   272  	probe.TimeoutSeconds = probeSetting.TimeoutSeconds
   273  	probe.FailureThreshold = probeSetting.FailureThreshold
   274  	statusProbeContainer.ReadinessProbe = probe
   275  	statusProbeContainer.StartupProbe.TCPSocket.Port = intstr.FromInt(probeSvcHTTPPort)
   276  }
   277  
   278  func buildRunningProbeContainer(characterType string, runningProbeContainer *corev1.Container,
   279  	probeSetting *appsv1alpha1.ClusterDefinitionProbe, probeSvcHTTPPort int) {
   280  	runningProbeContainer.Name = constant.RunningProbeContainerName
   281  	probe := &corev1.Probe{}
   282  	httpGet := &corev1.HTTPGetAction{}
   283  	httpGet.Path = fmt.Sprintf(checkRunningURIFormat, characterType)
   284  	httpGet.Port = intstr.FromInt(probeSvcHTTPPort)
   285  	probe.HTTPGet = httpGet
   286  	probe.PeriodSeconds = probeSetting.PeriodSeconds
   287  	probe.TimeoutSeconds = probeSetting.TimeoutSeconds
   288  	probe.FailureThreshold = probeSetting.FailureThreshold
   289  	runningProbeContainer.ReadinessProbe = probe
   290  	runningProbeContainer.StartupProbe.TCPSocket.Port = intstr.FromInt(probeSvcHTTPPort)
   291  }
   292  
   293  func volumeProtectionEnabled(component *SynthesizedComponent) bool {
   294  	return component.VolumeProtection != nil
   295  }
   296  
   297  func buildVolumeProtectionProbeContainer(characterType string, c *corev1.Container, probeSvcHTTPPort int) {
   298  	c.Name = constant.VolumeProtectionProbeContainerName
   299  	probe := &corev1.Probe{}
   300  	httpGet := &corev1.HTTPGetAction{}
   301  	httpGet.Path = fmt.Sprintf(volumeProtectionURIFormat, characterType)
   302  	httpGet.Port = intstr.FromInt(probeSvcHTTPPort)
   303  	probe.HTTPGet = httpGet
   304  	probe.PeriodSeconds = defaultVolumeProtectionProbe.PeriodSeconds
   305  	probe.TimeoutSeconds = defaultVolumeProtectionProbe.TimeoutSeconds
   306  	probe.FailureThreshold = defaultVolumeProtectionProbe.FailureThreshold
   307  	c.ReadinessProbe = probe
   308  	c.StartupProbe.TCPSocket.Port = intstr.FromInt(probeSvcHTTPPort)
   309  }
   310  
   311  func env4VolumeProtection(spec appsv1alpha1.VolumeProtectionSpec) corev1.EnvVar {
   312  	value, err := json.Marshal(spec)
   313  	if err != nil {
   314  		panic(fmt.Sprintf("marshal volume protection spec error: %s", err.Error()))
   315  	}
   316  	return corev1.EnvVar{
   317  		Name:  constant.KBEnvVolumeProtectionSpec,
   318  		Value: string(value),
   319  	}
   320  }