karmada/pkg/karmadactl/cmdinit/kubernetes/statefulset.go

348 lines
9.4 KiB
Go

package kubernetes
import (
"fmt"
"strings"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/options"
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/utils"
)
const (
etcdStatefulSetAndServiceName = "etcd"
etcdStatefulSetAPIVersion = "apps/v1"
etcdStatefulSetKind = "StatefulSet"
etcdContainerClientPortName = "client"
etcdContainerClientPort = 2379
etcdContainerServerPortName = "server"
etcdContainerServerPort = 2380
etcdContainerDataVolumeMountName = "etcd-data"
etcdContainerDataVolumeMountPath = "/var/lib/etcd"
etcdContainerConfigVolumeMountName = "etcd-config"
etcdContainerConfigDataMountPath = "/etc/etcd"
etcdConfigName = "etcd.conf"
etcdEnvPodName = "POD_NAME"
etcdEnvPodIP = "POD_IP"
//secrets name
etcdCertName = "etcd-cert"
)
var (
// appLabels remove via Labels karmada StatefulSet Deployment
appLabels = map[string]string{"karmada.io/bootstrapping": "app-defaults"}
etcdLabels = map[string]string{"app": etcdStatefulSetAndServiceName}
)
func (i CommandInitOption) etcdVolume() (*[]corev1.Volume, *corev1.PersistentVolumeClaim) {
var Volumes []corev1.Volume
secretVolume := corev1.Volume{
Name: etcdCertName,
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: etcdCertName,
},
},
}
configVolume := corev1.Volume{
Name: etcdContainerConfigVolumeMountName,
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
}
Volumes = append(Volumes, secretVolume, configVolume)
switch i.EtcdStorageMode {
case "PVC":
mode := corev1.PersistentVolumeFilesystem
persistentVolumeClaim := corev1.PersistentVolumeClaim{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "PersistentVolumeClaim",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: i.Namespace,
Name: etcdContainerDataVolumeMountName,
Labels: map[string]string{"karmada.io/bootstrapping": "pvc-defaults"},
},
Spec: corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
StorageClassName: &i.StorageClassesName,
VolumeMode: &mode,
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceStorage: resource.MustParse(i.EtcdPersistentVolumeSize),
},
},
},
}
return &Volumes, &persistentVolumeClaim
case "hostPath":
t := corev1.HostPathDirectoryOrCreate
hostPath := corev1.Volume{
Name: etcdContainerDataVolumeMountName,
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{
Path: i.EtcdHostDataPath,
Type: &t,
},
},
}
Volumes = append(Volumes, hostPath)
return &Volumes, nil
default:
emptyDir := corev1.Volume{
Name: etcdContainerDataVolumeMountName,
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
}
Volumes = append(Volumes, emptyDir)
return &Volumes, nil
}
}
func (i *CommandInitOption) etcdInitContainerCommand() []string {
etcdClusterConfig := ""
for v := int32(0); v < i.EtcdReplicas; v++ {
etcdClusterConfig += fmt.Sprintf("%s-%v=http://%s-%v.%s.%s.svc.cluster.local:%v", etcdStatefulSetAndServiceName, v, etcdStatefulSetAndServiceName, v, etcdStatefulSetAndServiceName, i.Namespace, etcdContainerServerPort) + ","
}
command := []string{
"sh",
"-c",
fmt.Sprintf(
`set -ex
cat <<EOF | tee %s/%s
name: ${%s}
client-transport-security:
client-cert-auth: true
trusted-ca-file: %s/%s.crt
key-file: %s/%s.key
cert-file: %s/%s.crt
peer-transport-security:
client-cert-auth: false
trusted-ca-file: %s/%s.crt
key-file: %s/%s.key
cert-file: %s/%s.crt
initial-cluster-state: new
initial-cluster-token: etcd-cluster
initial-cluster: %s
listen-peer-urls: http://${%s}:%v
listen-client-urls: https://${%s}:%v,http://127.0.0.1:%v
initial-advertise-peer-urls: http://${%s}:%v
advertise-client-urls: https://${%s}.%s.%s.svc.cluster.local:%v
data-dir: %s
`,
etcdContainerConfigDataMountPath, etcdConfigName,
etcdEnvPodName,
karmadaCertsVolumeMountPath, options.CaCertAndKeyName,
karmadaCertsVolumeMountPath, options.EtcdServerCertAndKeyName,
karmadaCertsVolumeMountPath, options.EtcdServerCertAndKeyName,
karmadaCertsVolumeMountPath, options.CaCertAndKeyName,
karmadaCertsVolumeMountPath, options.EtcdServerCertAndKeyName,
karmadaCertsVolumeMountPath, options.EtcdServerCertAndKeyName,
strings.TrimRight(etcdClusterConfig, ","),
etcdEnvPodIP, etcdContainerServerPort,
etcdEnvPodIP, etcdContainerClientPort, etcdContainerClientPort,
etcdEnvPodIP, etcdContainerServerPort,
etcdEnvPodName, etcdStatefulSetAndServiceName, i.Namespace, etcdContainerClientPort,
etcdContainerDataVolumeMountPath,
),
}
return command
}
func (i *CommandInitOption) makeETCDStatefulSet() *appsv1.StatefulSet {
Volumes, persistentVolumeClaim := i.etcdVolume()
// GroupsApiVersionResource
etcd := &appsv1.StatefulSet{
TypeMeta: metav1.TypeMeta{
APIVersion: etcdStatefulSetAPIVersion,
Kind: etcdStatefulSetKind,
},
ObjectMeta: metav1.ObjectMeta{
Name: etcdStatefulSetAndServiceName,
Namespace: i.Namespace,
Labels: appLabels,
},
}
// Probes
livenesProbe := &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
Exec: &corev1.ExecAction{
Command: []string{
"/bin/sh",
"-ec",
fmt.Sprintf("etcdctl get /registry --prefix --keys-only --endpoints http://127.0.0.1:%v", etcdContainerClientPort),
},
},
},
InitialDelaySeconds: 15,
FailureThreshold: 3,
PeriodSeconds: 60,
TimeoutSeconds: 5,
}
/* readinesProbe := &corev1.Probe{
Handler: corev1.Handler{
TCPSocket: &corev1.TCPSocketAction{
Port: intstr.IntOrString{
IntVal: etcdContainerClientPort,
},
},
},
InitialDelaySeconds: 5,
FailureThreshold: 3,
PeriodSeconds: 30,
TimeoutSeconds: 5,
}*/
// etcd Container
podSpec := corev1.PodSpec{
Affinity: &corev1.Affinity{
PodAntiAffinity: &corev1.PodAntiAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{
{
Weight: 100,
PodAffinityTerm: corev1.PodAffinityTerm{
TopologyKey: "kubernetes.io/hostname",
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "app",
Operator: metav1.LabelSelectorOpIn,
Values: []string{etcdStatefulSetAndServiceName},
},
},
},
},
},
},
},
},
Containers: []corev1.Container{
{
Name: etcdStatefulSetAndServiceName,
Image: i.EtcdImage,
Command: []string{
"/usr/local/bin/etcd",
fmt.Sprintf("--config-file=%s/%s", etcdContainerConfigDataMountPath, etcdConfigName),
},
Ports: []corev1.ContainerPort{
{
Name: etcdContainerClientPortName,
ContainerPort: etcdContainerClientPort,
Protocol: corev1.ProtocolTCP,
},
{
Name: etcdContainerServerPortName,
ContainerPort: etcdContainerServerPort,
Protocol: corev1.ProtocolTCP,
},
},
VolumeMounts: []corev1.VolumeMount{
{
Name: etcdContainerDataVolumeMountName,
ReadOnly: false,
MountPath: etcdContainerDataVolumeMountPath,
},
{
Name: etcdContainerConfigVolumeMountName,
ReadOnly: false,
MountPath: etcdContainerConfigDataMountPath,
},
{
Name: etcdCertName,
ReadOnly: true,
MountPath: karmadaCertsVolumeMountPath,
},
},
LivenessProbe: livenesProbe,
//ReadinessProbe: readinesProbe,
},
},
Volumes: *Volumes,
}
if i.EtcdStorageMode == "hostPath" && i.EtcdNodeSelectorLabels != "" {
podSpec.NodeSelector = utils.StringToMap(i.EtcdNodeSelectorLabels)
}
if i.EtcdStorageMode == "hostPath" && i.EtcdNodeSelectorLabels == "" {
podSpec.NodeSelector = map[string]string{"karmada.io/etcd": ""}
}
// InitContainers
podSpec.InitContainers = []corev1.Container{
{
Name: "etcd-init-conf",
Image: i.EtcdInitImage,
Command: i.etcdInitContainerCommand(),
VolumeMounts: []corev1.VolumeMount{
{
Name: etcdContainerConfigVolumeMountName,
ReadOnly: false,
MountPath: etcdContainerConfigDataMountPath,
},
},
Env: []corev1.EnvVar{
{
Name: etcdEnvPodName,
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "metadata.name",
},
},
},
{
Name: etcdEnvPodIP,
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "status.podIP",
},
},
},
},
},
}
// PodTemplateSpec
podTemplateSpec := corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Name: etcdStatefulSetAndServiceName,
Namespace: i.Namespace,
Labels: etcdLabels,
},
Spec: podSpec,
}
// StatefulSetSpec
etcd.Spec = appsv1.StatefulSetSpec{
Replicas: &i.EtcdReplicas,
Selector: &metav1.LabelSelector{
MatchLabels: etcdLabels,
},
Template: podTemplateSpec,
ServiceName: etcdStatefulSetAndServiceName,
}
// PVC
if persistentVolumeClaim != nil {
var pvc []corev1.PersistentVolumeClaim
pvc = append(pvc, *persistentVolumeClaim)
etcd.Spec.VolumeClaimTemplates = pvc
}
return etcd
}