github.com/IBM-Blockchain/fabric-operator@v1.0.4/pkg/offering/base/orderer/override/deployment.go (about)

     1  /*
     2   * Copyright contributors to the Hyperledger Fabric Operator project
     3   *
     4   * SPDX-License-Identifier: Apache-2.0
     5   *
     6   * Licensed under the Apache License, Version 2.0 (the "License");
     7   * you may not use this file except in compliance with the License.
     8   * You may obtain a copy of the License at:
     9   *
    10   * 	  http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing, software
    13   * distributed under the License is distributed on an "AS IS" BASIS,
    14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15   * See the License for the specific language governing permissions and
    16   * limitations under the License.
    17   */
    18  
    19  package override
    20  
    21  import (
    22  	"context"
    23  	"errors"
    24  	"fmt"
    25  	"path/filepath"
    26  
    27  	current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1"
    28  	commonapi "github.com/IBM-Blockchain/fabric-operator/pkg/apis/common"
    29  	"github.com/IBM-Blockchain/fabric-operator/pkg/initializer/common/config"
    30  	"github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources"
    31  	"github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources/container"
    32  	"github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources/deployment"
    33  	dep "github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources/deployment"
    34  	"github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources/serviceaccount"
    35  	"github.com/IBM-Blockchain/fabric-operator/pkg/offering/common"
    36  	"github.com/IBM-Blockchain/fabric-operator/pkg/util"
    37  	appsv1 "k8s.io/api/apps/v1"
    38  	corev1 "k8s.io/api/core/v1"
    39  	"k8s.io/apimachinery/pkg/api/resource"
    40  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    41  	"k8s.io/apimachinery/pkg/types"
    42  	logf "sigs.k8s.io/controller-runtime/pkg/log"
    43  )
    44  
    45  var log = logf.Log.WithName("orderer_deployment_override")
    46  
    47  type OrdererConfig interface {
    48  	MergeWith(interface{}, bool) error
    49  	ToBytes() ([]byte, error)
    50  	UsingPKCS11() bool
    51  	SetPKCS11Defaults(bool)
    52  	GetBCCSPSection() *commonapi.BCCSP
    53  	SetDefaultKeyStore()
    54  }
    55  
    56  // Container names
    57  const (
    58  	INIT      = "init"
    59  	ORDERER   = "orderer"
    60  	PROXY     = "proxy"
    61  	HSMCLIENT = "hsm-client"
    62  )
    63  
    64  func (o *Override) Deployment(object v1.Object, deployment *appsv1.Deployment, action resources.Action) error {
    65  	instance := object.(*current.IBPOrderer)
    66  	switch action {
    67  	case resources.Create:
    68  		return o.CreateDeployment(instance, deployment)
    69  	case resources.Update:
    70  		return o.UpdateDeployment(instance, deployment)
    71  	}
    72  
    73  	return nil
    74  }
    75  
    76  func (o *Override) CreateDeployment(instance *current.IBPOrderer, k8sDep *appsv1.Deployment) error {
    77  	var err error
    78  
    79  	if !instance.Spec.License.Accept {
    80  		return errors.New("user must accept license before continuing")
    81  	}
    82  
    83  	ordererType := instance.Spec.OrdererType
    84  	if ordererType == "" {
    85  		return errors.New("Orderer Type not provided")
    86  	}
    87  
    88  	systemChannelName := instance.Spec.SystemChannelName
    89  	if systemChannelName == "" {
    90  		return errors.New("System Channel Name not provided")
    91  	}
    92  
    93  	ordererOrgName := instance.Spec.OrgName
    94  	if ordererOrgName == "" {
    95  		return errors.New("Orderer Org Name not provided")
    96  	}
    97  
    98  	externalAddress := instance.Spec.ExternalAddress
    99  	if externalAddress == "" {
   100  		return errors.New("External Address not set")
   101  	}
   102  
   103  	deployment := dep.New(k8sDep)
   104  	deployment.SetServiceAccountName(serviceaccount.GetName(instance.GetName()))
   105  
   106  	orderer, err := deployment.GetContainer(ORDERER)
   107  	if err != nil {
   108  		return errors.New("orderer container not found in deployment spec")
   109  	}
   110  	grpcWeb, err := deployment.GetContainer(PROXY)
   111  	if err != nil {
   112  		return errors.New("proxy container not found in deployment spec")
   113  	}
   114  	_, err = deployment.GetContainer(INIT)
   115  	if err != nil {
   116  		return errors.New("init container not found in deployment spec")
   117  	}
   118  
   119  	err = o.CommonDeploymentOverrides(instance, deployment)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	deployment.SetImagePullSecrets(instance.Spec.ImagePullSecrets)
   125  
   126  	orderer.AppendConfigMapFromSourceIfMissing(instance.Name + "-env")
   127  
   128  	claimName := instance.Name + "-pvc"
   129  	if instance.Spec.CustomNames.PVC.Orderer != "" {
   130  		claimName = instance.Spec.CustomNames.PVC.Orderer
   131  	}
   132  	deployment.AppendPVCVolumeIfMissing("orderer-data", claimName)
   133  
   134  	grpcWeb.AppendEnvIfMissing("EXTERNAL_ADDRESS", externalAddress)
   135  
   136  	deployment.SetAffinity(o.GetAffinity(instance))
   137  
   138  	if o.AdminSecretExists(instance) {
   139  		deployment.AppendSecretVolumeIfMissing("ecert-admincerts", fmt.Sprintf("ecert-%s-admincerts", instance.Name))
   140  		orderer.AppendVolumeMountIfMissing("ecert-admincerts", "/certs/msp/admincerts")
   141  	}
   142  
   143  	deployment.AppendSecretVolumeIfMissing("ecert-cacerts", fmt.Sprintf("ecert-%s-cacerts", instance.Name))
   144  
   145  	co, err := instance.GetConfigOverride()
   146  	if err != nil {
   147  		return err
   148  	}
   149  
   150  	configOverride := co.(OrdererConfig)
   151  	if !configOverride.UsingPKCS11() {
   152  		deployment.AppendSecretVolumeIfMissing("ecert-keystore", fmt.Sprintf("ecert-%s-keystore", instance.Name))
   153  		orderer.AppendVolumeMountIfMissing("ecert-keystore", "/certs/msp/keystore")
   154  	}
   155  
   156  	deployment.AppendSecretVolumeIfMissing("ecert-signcert", fmt.Sprintf("ecert-%s-signcert", instance.Name))
   157  
   158  	secretName := fmt.Sprintf("tls-%s-cacerts", instance.Name)
   159  	ecertintercertSecret := fmt.Sprintf("ecert-%s-intercerts", instance.Name)
   160  	tlsintercertSecret := fmt.Sprintf("tls-%s-intercerts", instance.Name)
   161  	// Check if intermediate ecerts exists
   162  	if util.IntermediateSecretExists(o.Client, instance.Namespace, ecertintercertSecret) {
   163  		// Mount intermediate ecert
   164  		orderer.AppendVolumeMountIfMissing("ecert-intercerts", "/certs/msp/intermediatecerts")
   165  		deployment.AppendSecretVolumeIfMissing("ecert-intercerts", ecertintercertSecret)
   166  	}
   167  
   168  	// Check if intermediate tlscerts exists
   169  	if util.IntermediateSecretExists(o.Client, instance.Namespace, tlsintercertSecret) {
   170  		// Mount intermediate tls certs
   171  		orderer.AppendVolumeMountIfMissing("tls-intercerts", "/certs/msp/tlsintermediatecerts")
   172  		deployment.AppendSecretVolumeIfMissing("tls-intercerts", tlsintercertSecret)
   173  	}
   174  
   175  	deployment.AppendSecretVolumeIfMissing("tls-cacerts", secretName)
   176  	deployment.AppendSecretVolumeIfMissing("tls-keystore", fmt.Sprintf("tls-%s-keystore", instance.Name))
   177  	deployment.AppendSecretVolumeIfMissing("tls-signcert", fmt.Sprintf("tls-%s-signcert", instance.Name))
   178  	deployment.AppendConfigMapVolumeIfMissing("orderer-config", instance.Name+"-config")
   179  
   180  	if !instance.Spec.IsUsingChannelLess() {
   181  		deployment.AppendSecretVolumeIfMissing("orderer-genesis", fmt.Sprintf("%s-genesis", instance.Name))
   182  		orderer.AppendVolumeMountIfMissing("orderer-genesis", "/certs/genesis")
   183  	}
   184  
   185  	secret := &corev1.Secret{}
   186  	err = o.Client.Get(
   187  		context.TODO(),
   188  		types.NamespacedName{Name: instance.GetName() + "-secret", Namespace: instance.GetNamespace()},
   189  		secret,
   190  	)
   191  	if err == nil {
   192  		orderer.AppendEnvIfMissing("RESTART_OLD_RESOURCEVER", secret.ObjectMeta.ResourceVersion)
   193  	}
   194  
   195  	deployment.UpdateContainer(orderer)
   196  	if instance.UsingHSMProxy() {
   197  		orderer.AppendEnvIfMissing("PKCS11_PROXY_SOCKET", instance.Spec.HSM.PKCS11Endpoint)
   198  	} else if instance.IsHSMEnabled() {
   199  		deployment.AppendVolumeIfMissing(corev1.Volume{
   200  			Name: "shared",
   201  			VolumeSource: corev1.VolumeSource{
   202  				EmptyDir: &corev1.EmptyDirVolumeSource{
   203  					Medium: corev1.StorageMediumMemory,
   204  				},
   205  			},
   206  		})
   207  
   208  		orderer.AppendVolumeMountWithSubPathIfMissing("shared", "/hsm/lib", "hsm")
   209  
   210  		hsmConfig, err := config.ReadHSMConfig(o.Client, instance)
   211  		if err != nil {
   212  			return err
   213  		}
   214  
   215  		hsmSettings(instance, hsmConfig, orderer, deployment)
   216  		deployment.UpdateContainer(orderer)
   217  	}
   218  
   219  	return nil
   220  }
   221  
   222  func (o *Override) UpdateDeployment(instance *current.IBPOrderer, k8sDep *appsv1.Deployment) error {
   223  	deployment := dep.New(k8sDep)
   224  	err := o.CommonDeploymentOverrides(instance, deployment)
   225  	if err != nil {
   226  		return err
   227  	}
   228  
   229  	if instance.UsingHSMProxy() {
   230  		orderer := deployment.MustGetContainer(ORDERER)
   231  		orderer.UpdateEnv("PKCS11_PROXY_SOCKET", instance.Spec.HSM.PKCS11Endpoint)
   232  		deployment.UpdateContainer(orderer)
   233  	} else if instance.IsHSMEnabled() {
   234  		hsmInitCont := deployment.MustGetContainer(HSMCLIENT)
   235  		image := instance.Spec.Images
   236  		if image != nil {
   237  			hsmInitCont.SetImage(image.HSMImage, image.HSMTag)
   238  		}
   239  	}
   240  
   241  	return nil
   242  }
   243  
   244  func (o *Override) CommonDeploymentOverrides(instance *current.IBPOrderer, deployment *dep.Deployment) error {
   245  	orderer := deployment.MustGetContainer(ORDERER)
   246  	grpcProxy := deployment.MustGetContainer(PROXY)
   247  	initCont := deployment.MustGetContainer(INIT)
   248  
   249  	if instance.Spec.Replicas != nil {
   250  		if *instance.Spec.Replicas > 1 {
   251  			return errors.New("replicas > 1 not allowed in IBPOrderer")
   252  		}
   253  		deployment.SetReplicas(instance.Spec.Replicas)
   254  	}
   255  
   256  	resourcesRequest := instance.Spec.Resources
   257  	if resourcesRequest != nil {
   258  		if resourcesRequest.Init != nil {
   259  			err := initCont.UpdateResources(resourcesRequest.Init)
   260  			if err != nil {
   261  				return err
   262  			}
   263  		}
   264  		if resourcesRequest.Orderer != nil {
   265  			err := orderer.UpdateResources(resourcesRequest.Orderer)
   266  			if err != nil {
   267  				return err
   268  			}
   269  		}
   270  		if resourcesRequest.GRPCProxy != nil {
   271  			err := grpcProxy.UpdateResources(resourcesRequest.GRPCProxy)
   272  			if err != nil {
   273  				return err
   274  			}
   275  		}
   276  	}
   277  
   278  	image := instance.Spec.Images
   279  	if image != nil {
   280  		orderer.SetImage(image.OrdererImage, image.OrdererTag)
   281  		initCont.SetImage(image.OrdererInitImage, image.OrdererInitTag)
   282  		grpcProxy.SetImage(image.GRPCWebImage, image.GRPCWebTag)
   283  	}
   284  
   285  	if o.Config != nil && o.Config.Operator.Orderer.DisableProbes == "true" {
   286  		log.Info("Env var IBPOPERATOR_ORDERER_DISABLEPROBES set to 'true', disabling orderer container probes")
   287  		orderer.SetLivenessProbe(nil)
   288  		orderer.SetReadinessProbe(nil)
   289  		orderer.SetStartupProbe(nil)
   290  	}
   291  
   292  	deployment.UpdateContainer(orderer)
   293  	deployment.UpdateContainer(grpcProxy)
   294  	deployment.UpdateInitContainer(initCont)
   295  
   296  	return nil
   297  }
   298  
   299  func (o *Override) GetAffinity(instance *current.IBPOrderer) *corev1.Affinity {
   300  	arch := instance.Spec.Arch
   301  	zone := instance.Spec.Zone
   302  	region := instance.Spec.Region
   303  	nodeSelectorTerms := common.GetNodeSelectorTerms(arch, zone, region)
   304  
   305  	orgName := instance.Spec.OrgName
   306  	podAntiAffinity := common.GetPodAntiAffinity(orgName)
   307  
   308  	affinity := &corev1.Affinity{
   309  		PodAntiAffinity: podAntiAffinity,
   310  	}
   311  
   312  	if len(nodeSelectorTerms[0].MatchExpressions) != 0 {
   313  		affinity.NodeAffinity = &corev1.NodeAffinity{
   314  			RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
   315  				NodeSelectorTerms: nodeSelectorTerms,
   316  			},
   317  		}
   318  	}
   319  
   320  	return affinity
   321  }
   322  
   323  func (o *Override) AdminSecretExists(instance *current.IBPOrderer) bool {
   324  	secret := &corev1.Secret{}
   325  	err := o.Client.Get(context.TODO(), types.NamespacedName{
   326  		Name:      fmt.Sprintf("ecert-%s-admincerts", instance.Name),
   327  		Namespace: instance.Namespace}, secret)
   328  	if err != nil {
   329  		return false
   330  	}
   331  
   332  	return true
   333  }
   334  
   335  func hsmInitContainer(instance *current.IBPOrderer, hsmConfig *config.HSMConfig) *container.Container {
   336  	hsmLibraryPath := hsmConfig.Library.FilePath
   337  	hsmLibraryName := filepath.Base(hsmLibraryPath)
   338  
   339  	f := false
   340  	user := int64(0)
   341  	mountPath := "/shared"
   342  	return &container.Container{
   343  		Container: &corev1.Container{
   344  			Name:            "hsm-client",
   345  			Image:           fmt.Sprintf("%s:%s", instance.Spec.Images.HSMImage, instance.Spec.Images.HSMTag),
   346  			ImagePullPolicy: corev1.PullAlways,
   347  			Command: []string{
   348  				"sh",
   349  				"-c",
   350  				fmt.Sprintf("mkdir -p %s/hsm && dst=\"%s/hsm/%s\" && echo \"Copying %s to ${dst}\" && mkdir -p $(dirname $dst) && cp -r %s $dst", mountPath, mountPath, hsmLibraryName, hsmLibraryPath, hsmLibraryPath),
   351  			},
   352  			SecurityContext: &corev1.SecurityContext{
   353  				RunAsUser:    &user,
   354  				RunAsNonRoot: &f,
   355  			},
   356  			VolumeMounts: []corev1.VolumeMount{
   357  				corev1.VolumeMount{
   358  					Name:      "shared",
   359  					MountPath: mountPath,
   360  				},
   361  			},
   362  			Resources: corev1.ResourceRequirements{
   363  				Requests: corev1.ResourceList{
   364  					corev1.ResourceCPU:              resource.MustParse("0.1"),
   365  					corev1.ResourceMemory:           resource.MustParse("100Mi"),
   366  					corev1.ResourceEphemeralStorage: resource.MustParse("100Mi"),
   367  				},
   368  				Limits: corev1.ResourceList{
   369  					corev1.ResourceCPU:              resource.MustParse("2"),
   370  					corev1.ResourceMemory:           resource.MustParse("4Gi"),
   371  					corev1.ResourceEphemeralStorage: resource.MustParse("1Gi"),
   372  				},
   373  			},
   374  		},
   375  	}
   376  }
   377  
   378  func hsmSettings(instance *current.IBPOrderer, hsmConfig *config.HSMConfig, ordererCont container.Container, dep *deployment.Deployment) {
   379  	for _, v := range hsmConfig.GetVolumes() {
   380  		dep.AppendVolumeIfMissing(v)
   381  	}
   382  
   383  	for _, vm := range hsmConfig.GetVolumeMounts() {
   384  		ordererCont.AppendVolumeMountStructIfMissing(vm)
   385  	}
   386  
   387  	for _, env := range hsmConfig.GetEnvs() {
   388  		ordererCont.AppendEnvStructIfMissing(env)
   389  	}
   390  
   391  	if hsmConfig.Library.Auth != nil {
   392  		dep.Spec.Template.Spec.ImagePullSecrets = util.AppendPullSecretIfMissing(dep.Spec.Template.Spec.ImagePullSecrets, hsmConfig.Library.Auth.ImagePullSecret)
   393  	}
   394  
   395  	dep.AddInitContainer(*hsmInitContainer(instance, hsmConfig))
   396  
   397  	// If daemon settings are configured in HSM config, create a sidecar that is running the daemon image
   398  	if hsmConfig.Daemon != nil {
   399  		hsmDaemonSettings(instance, hsmConfig, ordererCont, dep)
   400  	}
   401  }
   402  
   403  func hsmDaemonSettings(instance *current.IBPOrderer, hsmConfig *config.HSMConfig, ordererCont container.Container, deployment *deployment.Deployment) {
   404  	// Unable to launch daemon if not running priviledged moe
   405  	t := true
   406  	ordererCont.SecurityContext.Privileged = &t
   407  	ordererCont.SecurityContext.AllowPrivilegeEscalation = &t
   408  
   409  	// Update command in deployment to ensure that deamon is running before starting the ca
   410  	ordererCont.Command = []string{
   411  		"sh",
   412  		"-c",
   413  		fmt.Sprintf("%s && orderer", config.DAEMON_CHECK_CMD),
   414  	}
   415  
   416  	// This is the shared volume where the file 'pkcsslotd-luanched' is touched to let
   417  	// other containers know that the daemon has successfully launched.
   418  	ordererCont.AppendVolumeMountIfMissing("shared", "/shared")
   419  
   420  	pvcVolumeName := "orderer-data"
   421  	// Certain token information requires to be stored in persistent store, the administrator
   422  	// responsible for configuring HSM sets the HSM config to point to the path where the PVC
   423  	// needs to be mounted.
   424  	var pvcMount *corev1.VolumeMount
   425  	for _, vm := range hsmConfig.MountPaths {
   426  		if vm.UsePVC {
   427  			pvcMount = &corev1.VolumeMount{
   428  				Name:      pvcVolumeName,
   429  				MountPath: vm.MountPath,
   430  			}
   431  		}
   432  	}
   433  
   434  	// If a pull secret is required to pull daemon image, update the deployment's image pull secrets
   435  	if hsmConfig.Daemon.Auth != nil {
   436  		deployment.Spec.Template.Spec.ImagePullSecrets = util.AppendPullSecretIfMissing(
   437  			deployment.Spec.Template.Spec.ImagePullSecrets,
   438  			hsmConfig.Daemon.Auth.ImagePullSecret,
   439  		)
   440  	}
   441  
   442  	// Add daemon container to the deployment
   443  	config.AddDaemonContainer(hsmConfig, deployment, instance.GetResource(current.HSMDAEMON), pvcMount)
   444  
   445  	// If a pvc mount has been configured in HSM config, set the volume mount on the ca container
   446  	// and PVC volume to deployment if missing
   447  	if pvcMount != nil {
   448  		ordererCont.AppendVolumeMountStructIfMissing(*pvcMount)
   449  		deployment.AppendPVCVolumeIfMissing(pvcVolumeName, instance.PVCName())
   450  	}
   451  }