Dans notre dernier article, nous avons présenté comment TiDB Operator orchestre les événements de boucle de contrôle pour gérer les cycles de vie des composants TiDB. Les TidbCluster
Le contrôleur gère les cycles de vie des composants TiDB, et le gestionnaire membre de chaque composant TiDB encapsule la logique de gestion spécifique de ce composant.
Dans cet article, je vais vous expliquer en détail comment nous implémentons une boucle de contrôle de composant en prenant PD comme exemple. Vous découvrirez le gestionnaire de membres PD et ses opérations de gestion du cycle de vie. Je comparerai également d’autres composants avec PD et montrerai leurs différences.
La boucle de contrôle PD
Le pilote de placement (PD) gère un cluster TiDB et planifie les régions dans le cluster. Le gestionnaire de membres PD maintient la logique de gestion du cycle de vie PD. La plupart de son code réside dans pkg/manager/member/pd_member_manager.go
, et d’autres codes liés à la mise à l’échelle, à la mise à niveau et au basculement se trouvent dans pd_scaler.go
, pd_upgrader.go
, et pd_failover.go
respectivement.
Comme illustré dans la partie III, la gestion du cycle de vie d’un composant nécessite les tâches suivantes :
- Service de synchronisation
- Démarrer la synchronisation StatefulSet
- État de la synchronisation
- Synchroniser ConfigMap
- Mise à jour continue
- Mise à l’échelle et mise à l’échelle
- Basculement
- Terminer la synchronisation StatefulSet
La synchronisation StatefulSet est la logique principale. D’autres tâches, telles que la synchronisation de l’état et la synchronisation de ConfigMap, sont définies comme des sous-fonctions et appelées par le gestionnaire de membres PD lorsqu’il synchronise StatefulSet.
Sync StatefulSet
Pour synchroniser StatefulSet, le gestionnaire de membres PD :
-
Obtient le PD actuel StatefulSet en utilisant
StatefulSetLister
.oldPDSetTmp, err := m.deps.StatefulSetLister.StatefulSets(ns).Get(controller.PDMemberName(tcName)) if err != nil && !errors.IsNotFound(err) { return fmt.Errorf("syncPDStatefulSetForTidbCluster: fail to get sts %s for cluster %s/%s, error: %s", controller.PDMemberName(tcName), ns, tcName, err) } setNotExist := errors.IsNotFound(err) oldPDSet := oldPDSetTmp.DeepCopy()
-
Obtient le dernier statut en utilisant
m.syncTidbClusterStatus(tc, oldPDSet)
.if err := m.syncTidbClusterStatus(tc, oldPDSet); err != nil { klog.Errorf("failed to sync TidbCluster: [%s/%s]'s status, error: %v", ns, tcName, err) }
-
Vérifie si le cluster TiDB suspend la synchronisation. Si c’est le cas, terminez le rapprochement suivant.
if tc.Spec.Paused { klog.V(4).Infof("tidb cluster %s/%s is paused, skip syncing for pd statefulset", tc.GetNamespace(), tc.GetName()) return nil }
-
Synchronise ConfigMap selon la dernière
tc.Spec
.cm, err := m.syncPDConfigMap(tc, oldPDSet)
-
Génère le dernier modèle StatefulSet en fonction de la dernière
tc.Spec
,tc.Status
, et ConfigMap obtenu à la dernière étape.newPDSet, err := getNewPDSetForTidbCluster(tc, cm)
-
Si le nouveau StatefulSet PD n’a pas encore été créé, créez d’abord le StatefulSet.
if setNotExist { if err := SetStatefulSetLastAppliedConfigAnnotation(newPDSet); err != nil { return err } if err := m.deps.StatefulSetControl.CreateStatefulSet(tc, newPDSet); err != nil { return err } tc.Status.PD.StatefulSet = &apps.StatefulSetStatus{} return controller.RequeueErrorf("TidbCluster: [%s/%s], waiting for PD cluster running", ns, tcName) }
-
Si un utilisateur configure une mise à niveau forcée à l’aide d’annotations, cette étape configure le StatefulSet pour effectuer directement une mise à niveau propagée. Ceci afin d’éviter un échec de mise à niveau si la boucle de réconciliation est bloquée.
if !tc.Status.PD.Synced && NeedForceUpgrade(tc.Annotations) { tc.Status.PD.Phase = v1alpha1.UpgradePhase setUpgradePartition(newPDSet, 0) errSTS := UpdateStatefulSet(m.deps.StatefulSetControl, tc, newPDSet, oldPDSet) return controller.RequeueErrorf("tidbcluster: [%s/%s]'s pd needs force upgrade, %v", ns, tcName, errSTS) }
-
Appelle la logique de mise à l’échelle implémentée dans
pd_scaler.go
.if err := m.scaler.Scale(tc, oldPDSet, newPDSet); err != nil { return err }
-
Appelle la logique de basculement implémentée dans
pd_failover.go
. La fonction vérifie d’abord si le cluster doit être restauré, puis vérifie si tous les pods sont démarrés et si tous les membres sont sains, et détermine enfin s’il doit commencer le basculement.if m.deps.CLIConfig.AutoFailover { if m.shouldRecover(tc) { m.failover.Recover(tc) } else if tc.PDAllPodsStarted() && !tc.PDAllMembersReady() || tc.PDAutoFailovering() { if err := m.failover.Failover(tc); err != nil { return err } } }
-
Appelle la logique de mise à niveau implémentée dans
pd_upgrader.go
. Lorsque le StatefulSet PD nouvellement généré est incohérent avec celui existant ou lorsque les deux StatefulSets sont cohérents maistc.Status.PD.Phase
estupgrade
, le gestionnaire des membres PD saisitupgrader
pour traiter la logique de mise à niveau progressive.if !templateEqual(newPDSet, oldPDSet) || tc.Status.PD.Phase == v1alpha1.UpgradePhase { if err := m.upgrader.Upgrade(tc, oldPDSet, newPDSet); err != nil { return err } }
-
Le StatefulSet PD est synchronisé et le nouveau StatefulSet est mis à jour vers le cluster Kubernetes.
Service de synchronisation
PD utilise à la fois des Services et des Services headless, gérés par syncPDServiceForTidbCluster
et syncPDHeadlessServiceForTidbCluster
.
L’adresse de service est utilisée dans TiDB, TiKV et TiFlash pour configurer le point de terminaison PD. Par exemple, TiDB utilise --path=${CLUSTER_NAME}-pd:2379
comme son adresse de service PD dans le paramètre de démarrage :
ARGS="--store=tikv
--advertise-address=${POD_NAME}.${HEADLESS_SERVICE_NAME}.${NAMESPACE}.svc
--path=${CLUSTER_NAME}-pd:2379
Le service sans tête fournit un identifiant unique pour chaque pod. Lorsque PD démarre, le PD Pod enregistre son point de terminaison dans les membres PD comme "${POD_NAME}.${PEER_SERVICE_NAME}.${NAMESPACE}.svc"
:
domain="${POD_NAME}.${PEER_SERVICE_NAME}.${NAMESPACE}.svc"
ARGS="--data-dir=/var/lib/pd
--name=${POD_NAME}
--peer-urls=http://0.0.0.0:2380
--advertise-peer-urls=http://${domain}:2380
--client-urls=http://0.0.0.0:2379
--advertise-client-urls=http://${domain}:2379
--config=/etc/pd/pd.toml
"
Synchroniser ConfigMap
PD utilise ConfigMap pour gérer les configurations et le script de démarrage. Les syncPDConfigMap
appels de fonction getPDConfigMap
pour obtenir la dernière ConfigMap et l’appliquer au cluster Kubernetes. ConfigMap gère les tâches suivantes :
-
Avoir
PD.Config
pour la synchronisation de suivi. Pour rester compatible avec les versions antérieures qui utilisent Helm, lorsque l’objet de configuration est vide, ConfigMap n’est pas synchronisé.config := tc.Spec.PD.Config if config == nil { return nil, nil }
-
Modifier la configuration liée à TLS. Étant donné que TiDB 4.0 et les versions antérieures ne prennent pas en charge le tableau de bord TiDB, le gestionnaire de membres PD ignore la configuration des certificats de tableau de bord pour PD dans les versions antérieures.
// override CA if tls enabled if tc.IsTLSClusterEnabled() { config.Set("security.cacert-path", path.Join(pdClusterCertPath, tlsSecretRootCAKey)) config.Set("security.cert-path", path.Join(pdClusterCertPath, corev1.TLSCertKey)) config.Set("security.key-path", path.Join(pdClusterCertPath, corev1.TLSPrivateKeyKey)) } // Versions below v4.0 do not support Dashboard if tc.Spec.TiDB != nil && tc.Spec.TiDB.IsTLSClientEnabled() && !tc.SkipTLSWhenConnectTiDB() && clusterVersionGE4 { config.Set("dashboard.tidb-cacert-path", path.Join(tidbClientCertPath, tlsSecretRootCAKey)) config.Set("dashboard.tidb-cert-path", path.Join(tidbClientCertPath, corev1.TLSCertKey)) config.Set("dashboard.tidb-key-path", path.Join(tidbClientCertPath, corev1.TLSPrivateKeyKey)) }
-
Transformez la configuration au format TOML afin que PD puisse la lire.
confText, err := config.MarshalTOML()
-
Générez le script de démarrage PD en utilisant
RenderPDStartScript
. Le modèle de script est stocké dans lepdStartScriptTpl
variable danspkg/manager/member/template.go
. Le script de démarrage PD est un script Bash.RenderPDStartScript
insère des variables et des annotations configurées par leTidbCluster
objet dans le script de démarrage. Ces variables et annotations sont utilisées pour le démarrage et le débogage de PD. -
Assemblez les configurations PD et le script de démarrage en tant qu’objet Kubernetes ConfigMap, et renvoyez le ConfigMap au
syncPDConfigMap
fonction.cm := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: controller.PDMemberName(tc.Name), Namespace: tc.Namespace, Labels: pdLabel, OwnerReferences: []metav1.OwnerReference{controller.GetOwnerRef(tc)}, }, Data: map[string]string{ "config-file": string(confText), "startup-script": startScript, }, }
Mise à l’échelle
La logique de mise à l’échelle est implémentée in pkg/manager/member/pd_scaler.go
. Le StatefulSet appelle le Scale
fonction de mise à l’échelle ou de mise à l’échelle dans le cluster PD.
Avant la mise à l’échelle, l’opérateur TiDB doit effectuer certaines opérations préliminaires. Par exemple, pour évoluer dans le cluster PD, l’opérateur TiDB doit transférer les leaders, mettre les membres hors ligne et ajouter des annotations à PersistentVolumeClaims (PVC) pour une suppression différée. Pour faire évoluer le cluster, l’opérateur TiDB doit supprimer les PVC précédemment conservés.
Une fois les opérations préliminaires terminées, l’opérateur TiDB ajuste le nombre de répliques StatefulSet et commence le processus de mise à l’échelle. Les opérations préliminaires minimisent l’impact de l’opération de mise à l’échelle.
Les Scale
fonction agit comme un routeur. En fonction de la direction, du pas et de la longueur de mise à l’échelle, il détermine le plan de mise à l’échelle à utiliser. Si la scaling
variable est un nombre positif, PD est mis à l’échelle ; sinon, PD est mis à l’échelle. Dans chaque opération de mise à l’échelle, PD n’est mis à l’échelle que d’une étape. Le plan spécifique est mis en œuvre par le ScaleIn
et ScaleOut
les fonctions.
func (s *pdScaler) Scale(meta metav1.Object, oldSet *apps.StatefulSet, newSet *apps.StatefulSet) error {
scaling, _, _, _ := scaleOne(oldSet, newSet)
if scaling > 0 {
return s.ScaleOut(meta, oldSet, newSet)
} else if scaling < 0 {
return s.ScaleIn(meta, oldSet, newSet)
}
return s.SyncAutoScalerAnn(meta, oldSet)
}
Mise à l’échelle
Avant que TiDB Operator mette un membre PD hors ligne, il doit transférer le chef. Sinon, lorsque le membre Leader est mis hors ligne, les autres membres PD sont contraints à une élection de Leader, ce qui affecte les performances du cluster. Par conséquent, l’opérateur TiDB doit transférer le leader au membre PD avec le plus petit ID. Cela garantit que le PD Leader n’est transféré qu’une seule fois.
Pour transférer le PD Leader, obtenez d’abord le PD Client et le PD Leader :
pdClient := controller.GetPDClient(s.deps.PDControl, tc)
leader, err := pdClient.GetPDLeader()
Lorsque le nom du leader est égal au nom du membre, l’opérateur TiDB transfère le leader. S’il ne reste qu’un membre, aucun autre membre n’est disponible pour le transfert, donc l’opérateur TiDB ignore le transfert du leader.
Après le transfert du chef, le ScaleIn
la fonction appelle les PD DeleteMember
API et supprime le membre des membres PD. Le membre PD est alors mis hors ligne. Enfin, la fonction appelle setReplicasAndDeleteSlots
pour ajuster le nombre de réplicas StatefulSet, et le processus de mise à l’échelle est terminé.
Mise à l’échelle
Avant que l’opérateur TiDB ne fasse évoluer le cluster PD, il doit supprimer les PVC différés en appelant deleteDeferDeletingPVC
. Lorsque l’opérateur TiDB a précédemment mis à l’échelle en PD, ces PVC ont été conservés pour une suppression différée afin d’assurer la fiabilité des données ; maintenant, l’opérateur TiDB doit les supprimer pour éviter que le cluster n’utilise d’anciennes données. Une fois ces PVC supprimés, l’opérateur TiDB n’a qu’à ajuster le nombre de répliques StatefulSet et à faire évoluer le cluster PD.
Que vous augmentiez ou augmentiez le cluster PD, l’opération est effectuée en configurant le nombre de réplicas StatefulSet….