Files
monok8s/clitools/pkg/render/controller.go

345 lines
8.3 KiB
Go

package render
import (
"bytes"
"fmt"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/apimachinery/pkg/util/intstr"
monov1alpha1 "example.com/monok8s/pkg/apis/monok8s/v1alpha1"
buildinfo "example.com/monok8s/pkg/buildinfo"
)
type ControllerConf struct {
Namespace string
Image string
Labels map[string]string
}
func RenderControllerDeployments(conf ControllerConf) (string, error) {
if conf.Namespace == "" {
return "", fmt.Errorf("namespace is required")
}
conf.Labels = map[string]string{
"app.kubernetes.io/name": monov1alpha1.ControllerName,
"app.kubernetes.io/component": "controller",
"app.kubernetes.io/part-of": "monok8s",
"app.kubernetes.io/managed-by": monov1alpha1.NodeControlName,
}
objs := []runtime.Object{
buildControllerServiceAccount(conf),
buildControllerClusterRole(conf),
buildControllerClusterRoleBinding(conf),
buildControllerDeployment(conf),
}
s := runtime.NewScheme()
_ = corev1.AddToScheme(s)
_ = rbacv1.AddToScheme(s)
_ = appsv1.AddToScheme(s)
serializer := json.NewYAMLSerializer(json.DefaultMetaFactory, s, s)
var buf bytes.Buffer
for i, obj := range objs {
if i > 0 {
if _, err := fmt.Fprintln(&buf, "---"); err != nil {
return "", err
}
}
if err := serializer.Encode(obj, &buf); err != nil {
return "", err
}
}
return buf.String(), nil
}
func buildControllerServiceAccount(conf ControllerConf) *corev1.ServiceAccount {
automount := true
return &corev1.ServiceAccount{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ServiceAccount",
},
ObjectMeta: metav1.ObjectMeta{
Name: monov1alpha1.ControllerName,
Namespace: conf.Namespace,
Labels: conf.Labels,
},
AutomountServiceAccountToken: &automount,
}
}
func buildControllerClusterRole(conf ControllerConf) *rbacv1.ClusterRole {
wantRules := []rbacv1.PolicyRule{
{
APIGroups: []string{monov1alpha1.Group},
Resources: []string{"osupgrades"},
Verbs: []string{"get", "list", "watch"},
},
{
APIGroups: []string{monov1alpha1.Group},
Resources: []string{"osupgrades/status"},
Verbs: []string{"get", "patch", "update"},
},
{
APIGroups: []string{monov1alpha1.Group},
Resources: []string{"osupgradeprogresses"},
Verbs: []string{"get", "create"},
},
{
APIGroups: []string{monov1alpha1.Group},
Resources: []string{"osupgradeprogresses/status"},
Verbs: []string{"update"},
},
{
APIGroups: []string{""},
Resources: []string{"nodes"},
Verbs: []string{"get", "list"},
},
}
return &rbacv1.ClusterRole{
TypeMeta: metav1.TypeMeta{
APIVersion: "rbac.authorization.k8s.io/v1",
Kind: "ClusterRole",
},
ObjectMeta: metav1.ObjectMeta{
Name: monov1alpha1.ControllerName,
Labels: conf.Labels,
},
Rules: wantRules,
}
}
func buildControllerClusterRoleBinding(conf ControllerConf) *rbacv1.ClusterRoleBinding {
wantSubjects := []rbacv1.Subject{
{
Kind: "ServiceAccount",
Name: monov1alpha1.ControllerName,
Namespace: conf.Namespace,
},
}
wantRoleRef := rbacv1.RoleRef{
APIGroup: rbacv1.GroupName,
Kind: "ClusterRole",
Name: monov1alpha1.ControllerName,
}
return &rbacv1.ClusterRoleBinding{
TypeMeta: metav1.TypeMeta{
APIVersion: "rbac.authorization.k8s.io/v1",
Kind: "ClusterRoleBinding",
},
ObjectMeta: metav1.ObjectMeta{
Name: monov1alpha1.ControllerName,
Labels: conf.Labels,
},
Subjects: wantSubjects,
RoleRef: wantRoleRef,
}
}
func buildControllerDeployment(conf ControllerConf) *appsv1.Deployment {
replicas := int32(1)
selectorLabels := map[string]string{
"app.kubernetes.io/name": monov1alpha1.ControllerName,
"app.kubernetes.io/component": "controller",
}
podLabels := mergeStringMaps(conf.Labels, selectorLabels)
runAsNonRoot := true
allowPrivilegeEscalation := false
userGroup := int64(65532)
image, pullPolicy := controllerImage(conf)
return &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
APIVersion: "apps/v1",
Kind: "Deployment",
},
ObjectMeta: metav1.ObjectMeta{
Name: monov1alpha1.ControllerName,
Namespace: conf.Namespace,
Labels: conf.Labels,
},
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
Selector: &metav1.LabelSelector{
MatchLabels: selectorLabels,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: podLabels,
},
Spec: corev1.PodSpec{
ServiceAccountName: monov1alpha1.ControllerName,
Containers: []corev1.Container{
{
Name: "controller",
Image: image,
ImagePullPolicy: pullPolicy,
Args: []string{
"controller",
"--namespace",
conf.Namespace,
},
Env: []corev1.EnvVar{
{
Name: "POD_NAME",
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
APIVersion: "v1",
FieldPath: "metadata.name",
},
},
},
{
Name: "POD_NAMESPACE",
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
APIVersion: "v1",
FieldPath: "metadata.namespace",
},
},
},
{
Name: "NODE_NAME",
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
APIVersion: "v1",
FieldPath: "spec.nodeName",
},
},
},
},
Ports: []corev1.ContainerPort{
{
Name: "http",
ContainerPort: 8080,
Protocol: corev1.ProtocolTCP,
},
{
Name: "https",
ContainerPort: 8443,
Protocol: corev1.ProtocolTCP,
},
},
LivenessProbe: &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/healthz",
Port: intstr.FromString("http"),
},
},
InitialDelaySeconds: 5,
PeriodSeconds: 60,
TimeoutSeconds: 2,
FailureThreshold: 3,
},
ReadinessProbe: &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/readyz",
Port: intstr.FromString("http"),
},
},
InitialDelaySeconds: 2,
PeriodSeconds: 5,
TimeoutSeconds: 2,
FailureThreshold: 3,
},
SecurityContext: &corev1.SecurityContext{
RunAsNonRoot: &runAsNonRoot,
RunAsUser: &userGroup,
RunAsGroup: &userGroup,
AllowPrivilegeEscalation: &allowPrivilegeEscalation,
},
},
},
NodeSelector: controllerNodeSelector(conf),
Affinity: controllerAffinity(conf),
},
},
},
}
}
func controllerImage(conf ControllerConf) (string, corev1.PullPolicy) {
if conf.Image != "" {
return conf.Image, corev1.PullIfNotPresent
}
return fmt.Sprintf("localhost/monok8s/node-control:%s", buildinfo.Version), corev1.PullNever
}
func controllerNodeSelector(conf ControllerConf) map[string]string {
if conf.Image != "" {
return nil
}
// Local image exists on managed nodes only.
return map[string]string{
monov1alpha1.NodeControlKey: "true",
}
}
func controllerAffinity(conf ControllerConf) *corev1.Affinity {
// Local image exists only on managed nodes, so in that mode we already use
// NodeSelector and should not fight placement with anti-affinity.
if conf.Image == "" {
return nil
}
return &corev1.Affinity{
PodAntiAffinity: &corev1.PodAntiAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{
{
Weight: 100,
PodAffinityTerm: corev1.PodAffinityTerm{
TopologyKey: corev1.LabelHostname,
LabelSelector: &metav1.LabelSelector{
MatchLabels: monov1alpha1.NodeAgentLabels(),
},
},
},
},
},
}
}
func mergeStringMaps(maps ...map[string]string) map[string]string {
var total int
for _, m := range maps {
total += len(m)
}
if total == 0 {
return nil
}
out := make(map[string]string, total)
for _, m := range maps {
for k, v := range m {
out[k] = v
}
}
return out
}