Pod Rolling update

introduce Pod events channel;
add parsing of the MaintenanceWindows section;
skip deleting Etcd key on cluster delete;
use external etcd host;
watch for tpr/pods in the namespace of the operator pod only;
This commit is contained in:
Murat Kabilov
2017-05-12 11:41:25 +02:00
parent 2b8956bd33
commit ae77fa15e8
23 changed files with 1763 additions and 790 deletions
+18 -8
View File
@@ -12,15 +12,25 @@ const (
ResourceCheckInterval = 3 * time.Second
ResourceCheckTimeout = 10 * time.Minute
ResourceName = TPRName + "s"
ResyncPeriod = 5 * time.Minute
PodLabelWaitTimeout = 10 * time.Minute
PodDeletionWaitTimeout = 10 * time.Minute
ResourceName = TPRName + "s"
ResyncPeriodTPR = 5 * time.Minute
ResyncPeriodPod = 5 * time.Minute
SuperuserName = "postgres"
ReplicationUsername = "replication"
//TODO: move to the operator spec
EtcdHost = "etcd-client.default.svc.cluster.local:2379"
SpiloImage = "registry.opensource.zalan.do/acid/spilo-9.6:1.2-p12"
PamRoleName = "zalandos"
PamConfiguration = "https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees"
EtcdHost = "etcd-client.default.svc.cluster.local:2379"
SpiloImage = "registry.opensource.zalan.do/acid/spilo-9.6:1.2-p12"
PamRoleName = "zalandos"
PamConfiguration = "https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees"
PasswordLength = 64
TeamsAPIUrl = "https://teams.example.com/api/"
UserSecretTemplate = "%s.%s.credentials.%s.%s"
PasswordLength = 64
TeamsAPIUrl = "https://teams.example.com/api/"
OAuthTokenSecretName = "postgresql-operator"
ServiceAccountName = "operator"
)
+1 -1
View File
@@ -33,7 +33,7 @@ func KubernetesClient(config *rest.Config) (client *kubernetes.Clientset, err er
return kubernetes.NewForConfig(config)
}
func IsKubernetesResourceAlreadyExistError(err error) bool {
func ResourceAlreadyExists(err error) bool {
return apierrors.IsAlreadyExists(err)
}
+265
View File
@@ -0,0 +1,265 @@
package resources
import (
"fmt"
"k8s.io/client-go/pkg/api/resource"
"k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/pkg/apis/apps/v1beta1"
extv1beta "k8s.io/client-go/pkg/apis/extensions/v1beta1"
"k8s.io/client-go/pkg/labels"
"k8s.io/client-go/pkg/util/intstr"
"github.bus.zalan.do/acid/postgres-operator/pkg/spec"
"github.bus.zalan.do/acid/postgres-operator/pkg/util/constants"
)
const (
superuserName = "postgres"
replicationUsername = "replication"
)
func credentialSecretName(clusterName, username string) string {
return fmt.Sprintf(
constants.UserSecretTemplate,
username,
clusterName,
constants.TPRName,
constants.TPRVendor)
}
func labelsSet(clusterName string) labels.Set {
return labels.Set{
"application": "spilo",
"spilo-cluster": clusterName,
}
}
func ResourceList(resources spec.Resources) *v1.ResourceList {
resourceList := v1.ResourceList{}
if resources.Cpu != "" {
resourceList[v1.ResourceCPU] = resource.MustParse(resources.Cpu)
}
if resources.Memory != "" {
resourceList[v1.ResourceMemory] = resource.MustParse(resources.Memory)
}
return &resourceList
}
func PodTemplate(cluster spec.ClusterName, resourceList *v1.ResourceList, dockerImage, pgVersion, etcdHost string) *v1.PodTemplateSpec {
envVars := []v1.EnvVar{
{
Name: "SCOPE",
Value: cluster.Name,
},
{
Name: "PGROOT",
Value: "/home/postgres/pgdata/pgroot",
},
{
Name: "ETCD_HOST",
Value: etcdHost,
},
{
Name: "POD_IP",
ValueFrom: &v1.EnvVarSource{
FieldRef: &v1.ObjectFieldSelector{
APIVersion: "v1",
FieldPath: "status.podIP",
},
},
},
{
Name: "POD_NAMESPACE",
ValueFrom: &v1.EnvVarSource{
FieldRef: &v1.ObjectFieldSelector{
APIVersion: "v1",
FieldPath: "metadata.namespace",
},
},
},
{
Name: "PGPASSWORD_SUPERUSER",
ValueFrom: &v1.EnvVarSource{
SecretKeyRef: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: credentialSecretName(cluster.Name, superuserName),
},
Key: "password",
},
},
},
{
Name: "PGPASSWORD_STANDBY",
ValueFrom: &v1.EnvVarSource{
SecretKeyRef: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: credentialSecretName(cluster.Name, replicationUsername),
},
Key: "password",
},
},
},
{
Name: "PAM_OAUTH2", //TODO: get from the operator tpr spec
Value: constants.PamConfiguration, //space before uid is obligatory
},
{
Name: "SPILO_CONFIGURATION", //TODO: get from the operator tpr spec
Value: fmt.Sprintf(`
postgresql:
bin_dir: /usr/lib/postgresql/%s/bin
bootstrap:
initdb:
- auth-host: md5
- auth-local: trust
users:
%s:
password: NULL
options:
- createdb
- nologin
pg_hba:
- hostnossl all all all reject
- hostssl all +%s all pam
- hostssl all all all md5`, pgVersion, constants.PamRoleName, constants.PamRoleName),
},
}
container := v1.Container{
Name: cluster.Name,
Image: dockerImage,
ImagePullPolicy: v1.PullAlways,
Resources: v1.ResourceRequirements{
Requests: *resourceList,
},
Ports: []v1.ContainerPort{
{
ContainerPort: 8008,
Protocol: v1.ProtocolTCP,
},
{
ContainerPort: 5432,
Protocol: v1.ProtocolTCP,
},
},
VolumeMounts: []v1.VolumeMount{
{
Name: "pgdata",
MountPath: "/home/postgres/pgdata", //TODO: fetch from manifesto
},
},
Env: envVars,
}
terminateGracePeriodSeconds := int64(30)
podSpec := v1.PodSpec{
ServiceAccountName: constants.ServiceAccountName,
TerminationGracePeriodSeconds: &terminateGracePeriodSeconds,
Volumes: []v1.Volume{
{
Name: "pgdata",
VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{}},
},
},
Containers: []v1.Container{container},
}
template := v1.PodTemplateSpec{
ObjectMeta: v1.ObjectMeta{
Labels: labelsSet(cluster.Name),
Namespace: cluster.Namespace,
Annotations: map[string]string{"pod.alpha.kubernetes.io/initialized": "true"},
},
Spec: podSpec,
}
return &template
}
func StatefulSet(cluster spec.ClusterName, podTemplate *v1.PodTemplateSpec, numberOfInstances int32) *v1beta1.StatefulSet {
statefulSet := &v1beta1.StatefulSet{
ObjectMeta: v1.ObjectMeta{
Name: cluster.Name,
Namespace: cluster.Namespace,
Labels: labelsSet(cluster.Name),
},
Spec: v1beta1.StatefulSetSpec{
Replicas: &numberOfInstances,
ServiceName: cluster.Name,
Template: *podTemplate,
},
}
return statefulSet
}
func UserSecrets(cluster spec.ClusterName, pgUsers map[string]spec.PgUser) (secrets map[string]*v1.Secret, err error) {
secrets = make(map[string]*v1.Secret, len(pgUsers))
namespace := cluster.Namespace
for username, pgUser := range pgUsers {
//Skip users with no password i.e. human users (they'll be authenticated using pam)
if pgUser.Password == "" {
continue
}
secret := v1.Secret{
ObjectMeta: v1.ObjectMeta{
Name: credentialSecretName(cluster.Name, username),
Namespace: namespace,
Labels: labelsSet(cluster.Name),
},
Type: v1.SecretTypeOpaque,
Data: map[string][]byte{
"username": []byte(pgUser.Name),
"password": []byte(pgUser.Password),
},
}
secrets[username] = &secret
}
return
}
func Service(cluster spec.ClusterName, allowedSourceRanges []string) *v1.Service {
service := &v1.Service{
ObjectMeta: v1.ObjectMeta{
Name: cluster.Name,
Namespace: cluster.Namespace,
Labels: labelsSet(cluster.Name),
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
Ports: []v1.ServicePort{{Port: 5432, TargetPort: intstr.IntOrString{IntVal: 5432}}},
LoadBalancerSourceRanges: allowedSourceRanges,
},
}
return service
}
func Endpoint(cluster spec.ClusterName) *v1.Endpoints {
endpoints := &v1.Endpoints{
ObjectMeta: v1.ObjectMeta{
Name: cluster.Name,
Namespace: cluster.Namespace,
Labels: labelsSet(cluster.Name),
},
}
return endpoints
}
func ThirdPartyResource(TPRName string) *extv1beta.ThirdPartyResource {
return &extv1beta.ThirdPartyResource{
ObjectMeta: v1.ObjectMeta{
//ThirdPartyResources are cluster-wide
Name: TPRName,
},
Versions: []extv1beta.APIVersion{
{Name: constants.TPRApiVersion},
},
Description: constants.TPRDescription,
}
}
+2 -2
View File
@@ -38,7 +38,7 @@ type Team struct {
type TeamsAPI struct {
url string
httpClient *http.Client
OauthToken string
OAuthToken string
}
func NewTeamsAPI(url string) *TeamsAPI {
@@ -57,7 +57,7 @@ func (t *TeamsAPI) TeamInfo(teamId string) (*Team, error) {
return nil, err
}
req.Header.Add("Authorization", "Bearer "+t.OauthToken)
req.Header.Add("Authorization", "Bearer "+t.OAuthToken)
resp, err := t.httpClient.Do(req)
if err != nil {
return nil, err
+18 -10
View File
@@ -1,11 +1,12 @@
package util
import (
"fmt"
"math/rand"
"time"
"github.bus.zalan.do/acid/postgres-operator/pkg/spec"
"k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/pkg/types"
)
var passwordChars = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
@@ -23,15 +24,22 @@ func RandomPassword(n int) string {
return string(b)
}
func FullObjectNameFromMeta(meta v1.ObjectMeta) string {
return FullObjectName(meta.Namespace, meta.Name)
}
//TODO: Remove in favour of FullObjectNameFromMeta
func FullObjectName(ns, name string) string {
if ns == "" {
ns = "default"
func NameFromMeta(meta v1.ObjectMeta) types.NamespacedName {
obj := types.NamespacedName{
Namespace: meta.Namespace,
Name: meta.Name,
}
return fmt.Sprintf("%s / %s", ns, name)
return obj
}
func PodClusterName(pod *v1.Pod) spec.ClusterName {
if name, ok := pod.Labels["spilo-cluster"]; ok {
return spec.ClusterName{
Namespace: pod.Namespace,
Name: name,
}
}
return spec.ClusterName{}
}