This is an automated email from the ASF dual-hosted git repository.
jimin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-seata-k8s.git
The following commit(s) were added to refs/heads/master by this push:
new ee6eef5 feature: support ConfigMap and Secret by changing env from
map to EnvVar (#17)
ee6eef5 is described below
commit ee6eef565e86680f0806382c733d9e46ff5e8e75
Author: Xiangkun Yin <[email protected]>
AuthorDate: Tue Jun 4 21:34:25 2024 +0800
feature: support ConfigMap and Secret by changing env from map to EnvVar
(#17)
---
README.md | 17 +++-
README.zh.md | 17 +++-
api/v1alpha1/seataserver_types.go | 2 +-
api/v1alpha1/zz_generated.deepcopy.go | 6 +-
.../operator.seata.apache.org_seataservers.yaml | 110 ++++++++++++++++++++-
config/rbac/role.yaml | 16 +++
controllers/seataserver_controller.go | 19 +++-
deploy/seata-server-cluster.yaml | 20 +++-
pkg/seata/fetchers.go | 70 +++++++++++++
pkg/seata/generators.go | 4 +-
pkg/seata/synchronizers.go | 14 +--
11 files changed, 268 insertions(+), 27 deletions(-)
diff --git a/README.md b/README.md
index 983abe1..0391667 100644
--- a/README.md
+++ b/README.md
@@ -93,8 +93,21 @@ For CRD details, you can visit
[operator.seata.apache.org_seataservers.yaml](con
requests:
storage: 5Gi
env:
- console.user.username: seata
- console.user.password: seata
+ - name: console.user.username
+ value: seata
+ - name: console.user.password
+ valueFrom:
+ secretKeyRef:
+ name: seata
+ key: password
+ ---
+ apiVersion: v1
+ kind: Secret
+ metadata:
+ name: seata
+ type: Opaque
+ data:
+ password: seata
```
## Method 2: Example without Using Operator
diff --git a/README.zh.md b/README.zh.md
index 86ffc87..fface34 100755
--- a/README.zh.md
+++ b/README.zh.md
@@ -98,8 +98,21 @@ https://github.com/seata/seata-docker
requests:
storage: 5Gi
env:
- console.user.username: seata
- console.user.password: seata
+ - name: console.user.username
+ value: seata
+ - name: console.user.password
+ valueFrom:
+ secretKeyRef:
+ name: seata
+ key: password
+ ---
+ apiVersion: v1
+ kind: Secret
+ metadata:
+ name: seata
+ type: Opaque
+ data:
+ password: seata
```
diff --git a/api/v1alpha1/seataserver_types.go
b/api/v1alpha1/seataserver_types.go
index cee9f53..1c4a761 100644
--- a/api/v1alpha1/seataserver_types.go
+++ b/api/v1alpha1/seataserver_types.go
@@ -78,7 +78,7 @@ type ContainerSpec struct {
ContainerName string `json:"containerName"`
Image string `json:"image"`
// +kubebuilder:validation:Optional
- Env map[string]string `json:"env"`
+ Env []apiv1.EnvVar `json:"env"`
// +kubebuilder:validation:Optional
Resources apiv1.ResourceRequirements `json:"resources"`
}
diff --git a/api/v1alpha1/zz_generated.deepcopy.go
b/api/v1alpha1/zz_generated.deepcopy.go
index 35277bb..54ee94f 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -192,9 +192,9 @@ func (in *ContainerSpec) DeepCopyInto(out *ContainerSpec) {
*out = *in
if in.Env != nil {
in, out := &in.Env, &out.Env
- *out = make(map[string]string, len(*in))
- for key, val := range *in {
- (*out)[key] = val
+ *out = make([]v1.EnvVar, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
}
}
in.Resources.DeepCopyInto(&out.Resources)
diff --git a/config/crd/bases/operator.seata.apache.org_seataservers.yaml
b/config/crd/bases/operator.seata.apache.org_seataservers.yaml
index 2c6883c..9d3bbf1 100644
--- a/config/crd/bases/operator.seata.apache.org_seataservers.yaml
+++ b/config/crd/bases/operator.seata.apache.org_seataservers.yaml
@@ -38,9 +38,113 @@ spec:
default: seata-server
type: string
env:
- additionalProperties:
- type: string
- type: object
+ items:
+ description: EnvVar represents an environment variable
present in
+ a Container.
+ properties:
+ name:
+ description: Name of the environment variable. Must be a
C_IDENTIFIER.
+ type: string
+ value:
+ description: 'Variable references $(VAR_NAME) are
expanded using
+ the previously defined environment variables in the
container
+ and any service environment variables. If a variable
cannot
+ be resolved, the reference in the input string will be
unchanged.
+ Double $$ are reduced to a single $, which allows for
escaping
+ the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will
produce the
+ string literal "$(VAR_NAME)". Escaped references will
never
+ be expanded, regardless of whether the variable exists
or
+ not. Defaults to "".'
+ type: string
+ valueFrom:
+ description: Source for the environment variable's
value. Cannot
+ be used if value is not empty.
+ properties:
+ configMapKeyRef:
+ description: Selects a key of a ConfigMap.
+ properties:
+ key:
+ description: The key to select.
+ type: string
+ name:
+ description: 'Name of the referent. More info:
https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+ TODO: Add other useful fields. apiVersion,
kind, uid?'
+ type: string
+ optional:
+ description: Specify whether the ConfigMap or
its key
+ must be defined
+ type: boolean
+ required:
+ - key
+ type: object
+ x-kubernetes-map-type: atomic
+ fieldRef:
+ description: 'Selects a field of the pod: supports
metadata.name,
+ metadata.namespace, `metadata.labels[''<KEY>'']`,
`metadata.annotations[''<KEY>'']`,
+ spec.nodeName, spec.serviceAccountName,
status.hostIP,
+ status.podIP, status.podIPs.'
+ properties:
+ apiVersion:
+ description: Version of the schema the FieldPath
is
+ written in terms of, defaults to "v1".
+ type: string
+ fieldPath:
+ description: Path of the field to select in the
specified
+ API version.
+ type: string
+ required:
+ - fieldPath
+ type: object
+ x-kubernetes-map-type: atomic
+ resourceFieldRef:
+ description: 'Selects a resource of the container:
only
+ resources limits and requests (limits.cpu,
limits.memory,
+ limits.ephemeral-storage, requests.cpu,
requests.memory
+ and requests.ephemeral-storage) are currently
supported.'
+ properties:
+ containerName:
+ description: 'Container name: required for
volumes,
+ optional for env vars'
+ type: string
+ divisor:
+ anyOf:
+ - type: integer
+ - type: string
+ description: Specifies the output format of the
exposed
+ resources, defaults to "1"
+ pattern:
^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+ x-kubernetes-int-or-string: true
+ resource:
+ description: 'Required: resource to select'
+ type: string
+ required:
+ - resource
+ type: object
+ x-kubernetes-map-type: atomic
+ secretKeyRef:
+ description: Selects a key of a secret in the pod's
namespace
+ properties:
+ key:
+ description: The key of the secret to select
from. Must
+ be a valid secret key.
+ type: string
+ name:
+ description: 'Name of the referent. More info:
https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+ TODO: Add other useful fields. apiVersion,
kind, uid?'
+ type: string
+ optional:
+ description: Specify whether the Secret or its
key must
+ be defined
+ type: boolean
+ required:
+ - key
+ type: object
+ x-kubernetes-map-type: atomic
+ type: object
+ required:
+ - name
+ type: object
+ type: array
image:
type: string
ports:
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index f2b3a78..0ab68b1 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -4,6 +4,22 @@ kind: ClusterRole
metadata:
name: manager-role
rules:
+- apiGroups:
+ - ""
+ resources:
+ - configmaps
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - ""
+ resources:
+ - secrets
+ verbs:
+ - get
+ - list
+ - watch
- apiGroups:
- apps
resources:
diff --git a/controllers/seataserver_controller.go
b/controllers/seataserver_controller.go
index a73f184..899da36 100644
--- a/controllers/seataserver_controller.go
+++ b/controllers/seataserver_controller.go
@@ -54,6 +54,8 @@ const RequeueSeconds = 10
//+kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch
+//+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch
+//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
@@ -183,7 +185,22 @@ func (r *SeataServerReconciler) updateStatefulSet(ctx
context.Context, s *seatav
s.Status.Synchronized = false
}
if readySize == newSize && !s.Status.Synchronized {
- if err = seata.SyncRaftCluster(ctx, s); err != nil {
+ username, password := "seata", "seata"
+ for _, env := range s.Spec.Env {
+ if env.Name == "console.user.username" {
+ username, err = seata.FetchEnvVar(ctx,
r.Client, s, env)
+ if err != nil {
+ logger.Error(err, "Failed to fetch Env
console.user.username")
+ }
+ }
+ if env.Name == "console.user.password" {
+ password, err = seata.FetchEnvVar(ctx,
r.Client, s, env)
+ if err != nil {
+ logger.Error(err, "Failed to fetch Env
console.user.password")
+ }
+ }
+ }
+ if err = seata.SyncRaftCluster(ctx, s, username, password); err
!= nil {
logger.Error(err, "Failed to synchronize the raft
cluster")
s.Status.Synchronized = false
} else {
diff --git a/deploy/seata-server-cluster.yaml b/deploy/seata-server-cluster.yaml
index 4499c7a..db81345 100644
--- a/deploy/seata-server-cluster.yaml
+++ b/deploy/seata-server-cluster.yaml
@@ -5,9 +5,25 @@ metadata:
namespace: default
spec:
serviceName: seata-server-cluster
- replicas: 2
- image: seataio/seata-server:2.0.0
+ replicas: 1
+ image: seataio/seata-server:latest
store:
resources:
requests:
storage: 5Gi
+ env:
+ - name: console.user.username
+ value: seata
+ - name: console.user.password
+ valueFrom:
+ secretKeyRef:
+ name: seata
+ key: password
+---
+apiVersion: v1
+kind: Secret
+metadata:
+ name: seata
+type: Opaque
+data:
+ password: MTIzNDU2
diff --git a/pkg/seata/fetchers.go b/pkg/seata/fetchers.go
new file mode 100644
index 0000000..26bca69
--- /dev/null
+++ b/pkg/seata/fetchers.go
@@ -0,0 +1,70 @@
+package seata
+
+import (
+ "context"
+ "fmt"
+ seatav1alpha1 "github.com/apache/seata-k8s/api/v1alpha1"
+ v1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/types"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func FetchEnvVar(ctx context.Context, c client.Client, cr
*seatav1alpha1.SeataServer, envVar v1.EnvVar) (string, error) {
+ if envVar.ValueFrom == nil {
+ return envVar.Value, nil
+ }
+
+ // Inspired by kubelet#makeEnvironmentVariables, determine the final
values of variables.
+ // See
https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/kubelet_pods.go#L694-L806
+ var result string
+ switch {
+ case envVar.ValueFrom.ConfigMapKeyRef != nil:
+ cm := envVar.ValueFrom.ConfigMapKeyRef
+ name := cm.Name
+ key := cm.Key
+ optional := cm.Optional != nil && *cm.Optional
+
+ configMap := &v1.ConfigMap{}
+ err := c.Get(ctx, types.NamespacedName{Name: name, Namespace:
cr.Namespace}, configMap)
+ if err != nil {
+ if errors.IsNotFound(err) && optional {
+ // ignore error when marked optional
+ return result, nil
+ }
+ return result, err
+ }
+ runtimeVal, ok := configMap.Data[key]
+ if !ok {
+ if optional {
+ return result, nil
+ }
+ return result, fmt.Errorf("couldn't find key %v in
ConfigMap %v/%v", key, cr.Namespace, name)
+ }
+ result = runtimeVal
+ case envVar.ValueFrom.SecretKeyRef != nil:
+ s := envVar.ValueFrom.SecretKeyRef
+ name := s.Name
+ key := s.Key
+ optional := s.Optional != nil && *s.Optional
+ secret := &v1.Secret{}
+ err := c.Get(ctx, types.NamespacedName{Name: name, Namespace:
cr.Namespace}, secret)
+ if err != nil {
+ if errors.IsNotFound(err) && optional {
+ // ignore error when marked optional
+ return result, nil
+ }
+ return result, err
+ }
+ runtimeValBytes, ok := secret.Data[key]
+ if !ok {
+ if optional {
+ return result, nil
+ }
+ return result, fmt.Errorf("couldn't find key %v in
Secret %v/%v", key, cr.Namespace, name)
+ }
+ runtimeVal := string(runtimeValBytes)
+ result = runtimeVal
+ }
+ return result, nil
+}
diff --git a/pkg/seata/generators.go b/pkg/seata/generators.go
index 76f2385..21addc6 100644
--- a/pkg/seata/generators.go
+++ b/pkg/seata/generators.go
@@ -137,8 +137,8 @@ func MakeStatefulSet(s *seatav1alpha1.SeataServer)
*appsv1.StatefulSet {
addr := utils.ConcatRaftServerAddress(s)
envs = append(envs, apiv1.EnvVar{Name: "server.raft.serverAddr", Value:
addr})
- for k, v := range s.Spec.Env {
- envs = append(envs, apiv1.EnvVar{Name: k, Value: v})
+ for _, env := range s.Spec.Env {
+ envs = append(envs, env)
}
container.Env = envs
diff --git a/pkg/seata/synchronizers.go b/pkg/seata/synchronizers.go
index 71c0da2..6a12c33 100644
--- a/pkg/seata/synchronizers.go
+++ b/pkg/seata/synchronizers.go
@@ -33,17 +33,9 @@ type rspData struct {
Success bool `json:"success"`
}
-func changeCluster(s *seatav1alpha1.SeataServer, i int32) error {
+func changeCluster(s *seatav1alpha1.SeataServer, i int32, username string,
password string) error {
client := http.Client{}
host := fmt.Sprintf("%s-%d.%s.%s.svc:%d", s.Name, i,
s.Spec.ServiceName, s.Namespace, s.Spec.Ports.ConsolePort)
- username, ok := s.Spec.Env["console.user.username"]
- if !ok {
- username = "seata"
- }
- password, ok := s.Spec.Env["console.user.password"]
- if !ok {
- password = "seata"
- }
values := map[string]string{"username": username, "password": password}
jsonValue, _ := json.Marshal(values)
@@ -100,7 +92,7 @@ func changeCluster(s *seatav1alpha1.SeataServer, i int32)
error {
return nil
}
-func SyncRaftCluster(ctx context.Context, s *seatav1alpha1.SeataServer) error {
+func SyncRaftCluster(ctx context.Context, s *seatav1alpha1.SeataServer,
username string, password string) error {
logger := log.FromContext(ctx)
group, childContext := errgroup.WithContext(ctx)
@@ -111,7 +103,7 @@ func SyncRaftCluster(ctx context.Context, s
*seatav1alpha1.SeataServer) error {
case <-childContext.Done():
return nil
default:
- err := changeCluster(s, finalI)
+ err := changeCluster(s, finalI, username,
password)
if err != nil {
logger.Error(err, fmt.Sprintf("fail to
SyncRaftCluster at %d-th pod", finalI))
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]