forked from rook/rook
The number of admission controller pods is 2. When there is only one node as the CI environment, one of these becomes Pending state. Although it's harmless, it's better to reduce this value to 1. Signed-off-by: Satoru Takeuchi <satoru.takeuchi@gmail.com>
219 lines
6.1 KiB
Go
219 lines
6.1 KiB
Go
/*
|
|
Copyright 2020 The Rook Authors. All rights reserved.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package operator
|
|
|
|
import (
|
|
"os"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/rook/rook/pkg/clusterd"
|
|
"github.com/rook/rook/pkg/operator/ceph/csi"
|
|
"github.com/rook/rook/pkg/operator/k8sutil"
|
|
v1 "k8s.io/api/apps/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/util/intstr"
|
|
)
|
|
|
|
const (
|
|
appName = "rook-ceph-admission-controller"
|
|
secretVolumeName = "webhook-certificates" // #nosec G101 This is just a var name, not a real secret
|
|
serviceAccountName = "rook-ceph-admission-controller"
|
|
portName = "webhook-api"
|
|
servicePort int32 = 443
|
|
serverPort int32 = 8079
|
|
tlsDir = "/etc/webhook"
|
|
)
|
|
|
|
var (
|
|
namespace = os.Getenv(k8sutil.PodNamespaceEnvVar)
|
|
)
|
|
|
|
func isSecretPresent(context *clusterd.Context) (bool, error) {
|
|
logger.Infof("looking for secret %q", appName)
|
|
_, err := context.Clientset.CoreV1().Secrets(namespace).Get(appName, metav1.GetOptions{})
|
|
if err != nil {
|
|
// If secret is not found. All good ! Proceed with rook without admission controllers
|
|
if apierrors.IsNotFound(err) {
|
|
logger.Infof("secret %q not found. proceeding without the admission controller", appName)
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func createWebhookService(context *clusterd.Context) error {
|
|
webhookService := corev1.Service{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: appName,
|
|
Namespace: namespace,
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: []corev1.ServicePort{
|
|
{
|
|
Port: servicePort,
|
|
TargetPort: intstr.IntOrString{
|
|
IntVal: serverPort,
|
|
},
|
|
},
|
|
},
|
|
|
|
Selector: map[string]string{
|
|
k8sutil.AppAttr: appName,
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := k8sutil.CreateOrUpdateService(context.Clientset, namespace, &webhookService)
|
|
if err != nil && !apierrors.IsAlreadyExists(err) {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// StartControllerIfSecretPresent will initialize the webhook if secret is detected
|
|
func StartControllerIfSecretPresent(context *clusterd.Context, admissionImage string) error {
|
|
isPresent, err := isSecretPresent(context)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to retrieve secret")
|
|
}
|
|
if isPresent {
|
|
err = initWebhook(context, admissionImage)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to initialize webhook")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func initWebhook(context *clusterd.Context, admissionImage string) error {
|
|
// At this point volume should be mounted, so proceed with creating the service and validatingwebhookconfig
|
|
err := createWebhookService(context)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to create service")
|
|
}
|
|
err = createWebhookDeployment(context, admissionImage)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to create deployment")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func createWebhookDeployment(context *clusterd.Context, admissionImage string) error {
|
|
logger.Info("creating admission controller pods")
|
|
admission_parameters := []string{"ceph",
|
|
"admission-controller"}
|
|
secretVolume := getSecretVolume()
|
|
secretVolumeMount := getSecretVolumeMount()
|
|
|
|
antiAffinity := csi.GetPodAntiAffinity(k8sutil.AppAttr, appName)
|
|
admissionControllerDeployment := getDeployment(context, secretVolume, antiAffinity, admissionImage, admission_parameters, secretVolumeMount)
|
|
|
|
err := k8sutil.CreateDeployment(context.Clientset, appName, namespace, &admissionControllerDeployment)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to create admission-controller deployment")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getDeployment(context *clusterd.Context, secretVolume corev1.Volume, antiAffinity corev1.PodAntiAffinity,
|
|
admissionImage string, admission_parameters []string, secretVolumeMount corev1.VolumeMount) v1.Deployment {
|
|
var replicas int32 = 2
|
|
nodes, err := context.Clientset.CoreV1().Nodes().List(metav1.ListOptions{})
|
|
if err == nil {
|
|
if len(nodes.Items) == 1 {
|
|
replicas = 1
|
|
}
|
|
} else {
|
|
logger.Errorf("failed to get nodes. Defaulting the number of replicas of admission controller pods to 2. %v", err)
|
|
}
|
|
|
|
admissionControllerDeployment := v1.Deployment{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: appName,
|
|
Namespace: namespace,
|
|
},
|
|
Spec: v1.DeploymentSpec{
|
|
Replicas: &replicas,
|
|
Selector: &metav1.LabelSelector{
|
|
MatchLabels: map[string]string{
|
|
k8sutil.AppAttr: appName,
|
|
},
|
|
},
|
|
Template: corev1.PodTemplateSpec{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: appName,
|
|
Namespace: namespace,
|
|
Labels: map[string]string{
|
|
k8sutil.AppAttr: appName,
|
|
},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Volumes: []corev1.Volume{
|
|
secretVolume,
|
|
},
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: appName,
|
|
Image: admissionImage,
|
|
Args: admission_parameters,
|
|
Ports: []corev1.ContainerPort{
|
|
{
|
|
Name: portName,
|
|
ContainerPort: serverPort,
|
|
},
|
|
},
|
|
VolumeMounts: []corev1.VolumeMount{
|
|
secretVolumeMount,
|
|
},
|
|
},
|
|
},
|
|
ServiceAccountName: serviceAccountName,
|
|
Affinity: &corev1.Affinity{
|
|
PodAntiAffinity: &antiAffinity,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
return admissionControllerDeployment
|
|
}
|
|
|
|
func getSecretVolumeMount() corev1.VolumeMount {
|
|
secretVolumeMount := corev1.VolumeMount{
|
|
Name: secretVolumeName,
|
|
ReadOnly: true,
|
|
MountPath: tlsDir,
|
|
}
|
|
return secretVolumeMount
|
|
}
|
|
|
|
func getSecretVolume() corev1.Volume {
|
|
secretVolume := corev1.Volume{
|
|
Name: secretVolumeName,
|
|
VolumeSource: corev1.VolumeSource{
|
|
Secret: &corev1.SecretVolumeSource{
|
|
SecretName: appName,
|
|
},
|
|
},
|
|
}
|
|
return secretVolume
|
|
}
|