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 }