github.com/IBM-Blockchain/fabric-operator@v1.0.4/pkg/offering/base/peer/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 "encoding/json" 24 "fmt" 25 "path/filepath" 26 "strings" 27 28 current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1" 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 "github.com/IBM-Blockchain/fabric-operator/pkg/util/image" 38 "github.com/IBM-Blockchain/fabric-operator/version" 39 "github.com/pkg/errors" 40 appsv1 "k8s.io/api/apps/v1" 41 corev1 "k8s.io/api/core/v1" 42 "k8s.io/apimachinery/pkg/api/resource" 43 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 44 "k8s.io/apimachinery/pkg/types" 45 ) 46 47 // Container names 48 const ( 49 INIT = "init" 50 PEER = "peer" 51 DIND = "dind" 52 PROXY = "proxy" 53 FLUENTD = "chaincode-logs" 54 COUCHDB = "couchdb" 55 COUCHDBINIT = "couchdbinit" 56 CCLAUNCHER = "chaincode-launcher" 57 HSMCLIENT = "hsm-client" 58 ) 59 60 type CoreConfig interface { 61 UsingPKCS11() bool 62 } 63 64 func (o *Override) Deployment(object v1.Object, deployment *appsv1.Deployment, action resources.Action) error { 65 instance := object.(*current.IBPPeer) 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.IBPPeer, k8sDep *appsv1.Deployment) error { 77 var err error 78 name := instance.GetName() 79 80 if !instance.Spec.License.Accept { 81 return errors.New("user must accept license before continuing") 82 } 83 84 mspID := instance.Spec.MSPID 85 if mspID == "" { 86 return errors.New("failed to provide MSP ID for peer") 87 } 88 89 deployment := dep.New(k8sDep) 90 initContainer, err := deployment.GetContainer(INIT) 91 if err != nil { 92 return errors.New("init container not found in deployment spec") 93 } 94 peerContainer, err := deployment.GetContainer(PEER) 95 if err != nil { 96 return errors.New("peer container not found in deployment spec") 97 } 98 grpcwebContainer, err := deployment.GetContainer(PROXY) 99 if err != nil { 100 return errors.New("grpc container not found in deployment spec") 101 } 102 103 stateDB := instance.Spec.StateDb 104 if instance.UsingCouchDB() { 105 if !deployment.ContainerExists(COUCHDB) { // If coucdb container exists, don't need to create it again 106 stateDB = "CouchDB" 107 err = o.CreateCouchDBContainers(instance, deployment) 108 if err != nil { 109 return err 110 } 111 } 112 } else if instance.Spec.UsingLevelDB() { 113 stateDB = "goleveldb" 114 115 peerContainer.AppendVolumeMountWithSubPathIfMissing("db-data", "/data/peer/ledgersData/stateLeveldb/", "data") 116 initContainer.AppendVolumeMountWithSubPathIfMissing("db-data", "/data/peer/ledgersData/stateLeveldb/", "data") 117 118 deployment.UpdateContainer(peerContainer) 119 deployment.UpdateInitContainer(initContainer) 120 } else { 121 return errors.New("unsupported StateDB type") 122 } 123 124 err = o.CommonDeploymentOverrides(instance, deployment) 125 if err != nil { 126 return err 127 } 128 129 // At this point we know init, peer, and proxy containers exists. 130 // Can use MustGetContainer to avoid handling error 131 peerContainer = deployment.MustGetContainer(PEER) 132 grpcwebContainer = deployment.MustGetContainer(PROXY) 133 134 deployment.SetImagePullSecrets(instance.Spec.ImagePullSecrets) 135 deployment.SetServiceAccountName(serviceaccount.GetName(name)) 136 deployment.SetAffinity(o.GetAffinity(instance)) 137 138 peerContainer.AppendEnvIfMissing("CORE_PEER_ID", instance.Name) 139 peerContainer.AppendEnvIfMissing("CORE_PEER_LOCALMSPID", mspID) 140 141 claimName := instance.Name + "-statedb-pvc" 142 if instance.Spec.CustomNames.PVC.StateDB != "" { 143 claimName = instance.Spec.CustomNames.PVC.StateDB 144 } 145 deployment.AppendPVCVolumeIfMissing("db-data", claimName) 146 147 peerContainer.AppendEnvIfMissing("CORE_LEDGER_STATE_STATEDATABASE", stateDB) 148 149 claimName = instance.Name + "-pvc" 150 if instance.Spec.CustomNames.PVC.Peer != "" { 151 claimName = instance.Spec.CustomNames.PVC.Peer 152 } 153 deployment.AppendPVCVolumeIfMissing("fabric-peer-0", claimName) 154 155 deployment.AppendConfigMapVolumeIfMissing("fluentd-config", instance.Name+"-fluentd") 156 157 ecertintercertSecret := fmt.Sprintf("ecert-%s-intercerts", instance.Name) 158 tlsintercertSecret := fmt.Sprintf("tls-%s-intercerts", instance.Name) 159 secretName := fmt.Sprintf("tls-%s-cacerts", instance.Name) 160 // Check if intermediate ecerts exists 161 if util.IntermediateSecretExists(o.Client, instance.Namespace, ecertintercertSecret) { 162 peerContainer.AppendVolumeMountIfMissing("ecert-intercerts", "/certs/msp/intermediatecerts") 163 deployment.AppendSecretVolumeIfMissing("ecert-intercerts", ecertintercertSecret) 164 } 165 166 // Check if intermediate tlscerts exists 167 if util.IntermediateSecretExists(o.Client, instance.Namespace, tlsintercertSecret) { 168 peerContainer.AppendVolumeMountIfMissing("tls-intercerts", "/certs/msp/tlsintermediatecerts") 169 deployment.AppendSecretVolumeIfMissing("tls-intercerts", tlsintercertSecret) 170 } 171 172 tlsCACertsSecret, err := o.GetTLSCACertsSecret(instance, secretName) 173 if err != nil { 174 return err 175 } 176 177 var certsData string 178 count := 0 179 for key, _ := range tlsCACertsSecret.Data { 180 v := fmt.Sprintf("/certs/msp/tlscacerts/%s", key) 181 if count == 0 { 182 certsData = certsData + v 183 } else { 184 certsData = certsData + " " + v 185 } 186 count++ 187 } 188 peerContainer.AppendEnvIfMissingOverrideIfPresent("CORE_OPERATIONS_TLS_CLIENTROOTCAS_FILES", certsData) 189 peerContainer.AppendEnvIfMissingOverrideIfPresent("CORE_PEER_TLS_ROOTCERT_FILE", certsData) 190 grpcwebContainer.AppendEnvIfMissingOverrideIfPresent("SERVER_TLS_CLIENT_CA_FILES", certsData) 191 peerContainer.AppendEnvIfMissingOverrideIfPresent("CORE_PEER_TLS_ROOTCERT_FILE", certsData) 192 193 // Check if intermediate tlscerts exists 194 if util.IntermediateSecretExists(o.Client, instance.Namespace, tlsintercertSecret) { 195 secretName := fmt.Sprintf("tls-%s-intercerts", instance.Name) 196 tlsCAInterCertsSecret, err := o.GetTLSCACertsSecret(instance, secretName) 197 if err != nil { 198 return err 199 } 200 201 var certsData string 202 count := 0 203 for key, _ := range tlsCAInterCertsSecret.Data { 204 v := fmt.Sprintf("/certs/msp/tlsintermediatecerts/%s", key) 205 if count == 0 { 206 certsData = certsData + v 207 } else { 208 certsData = certsData + " " + v 209 } 210 count++ 211 } 212 peerContainer.AppendEnvIfMissingOverrideIfPresent("CORE_PEER_TLS_ROOTCERT_FILE", certsData) 213 } 214 215 if o.AdminSecretExists(instance) { 216 deployment.AppendSecretVolumeIfMissing("ecert-admincerts", fmt.Sprintf("ecert-%s-admincerts", instance.Name)) 217 peerContainer.AppendVolumeMountIfMissing("ecert-admincerts", "/certs/msp/admincerts") 218 } 219 220 co, err := instance.GetConfigOverride() 221 if err != nil { 222 return errors.Wrap(err, "failed to get configoverride") 223 } 224 225 configOverride := co.(CoreConfig) 226 if !configOverride.UsingPKCS11() { 227 deployment.AppendSecretVolumeIfMissing("ecert-keystore", fmt.Sprintf("ecert-%s-keystore", instance.Name)) 228 peerContainer.AppendVolumeMountIfMissing("ecert-keystore", "/certs/msp/keystore") 229 } 230 231 deployment.AppendSecretVolumeIfMissing("ecert-cacerts", fmt.Sprintf("ecert-%s-cacerts", instance.Name)) 232 deployment.AppendSecretVolumeIfMissing("ecert-signcert", fmt.Sprintf("ecert-%s-signcert", instance.Name)) 233 deployment.AppendSecretVolumeIfMissing("tls-cacerts", fmt.Sprintf("tls-%s-cacerts", instance.Name)) 234 deployment.AppendSecretVolumeIfMissing("tls-keystore", fmt.Sprintf("tls-%s-keystore", instance.Name)) 235 deployment.AppendSecretVolumeIfMissing("tls-signcert", fmt.Sprintf("tls-%s-signcert", instance.Name)) 236 237 if o.OrdererCACertsSecretExists(instance) { 238 deployment.AppendSecretVolumeIfMissing("orderercacerts", fmt.Sprintf("%s-orderercacerts", instance.Name)) 239 peerContainer.AppendVolumeMountIfMissing("orderercacerts", "/orderer/certs") 240 } 241 242 deployment.AppendConfigMapVolumeIfMissing("peer-config", instance.Name+"-config") 243 244 secret := &corev1.Secret{} 245 err = o.Client.Get( 246 context.TODO(), 247 types.NamespacedName{Name: instance.GetName() + "-secret", Namespace: instance.GetNamespace()}, 248 secret, 249 ) 250 if err == nil { 251 peerContainer.AppendEnvIfMissing("RESTART_OLD_RESOURCEVER", secret.ObjectMeta.ResourceVersion) 252 } 253 254 deployment.UpdateContainer(grpcwebContainer) 255 256 if instance.UsingHSMProxy() { 257 peerContainer.AppendEnvIfMissing("PKCS11_PROXY_SOCKET", instance.Spec.HSM.PKCS11Endpoint) 258 } else if instance.IsHSMEnabled() { 259 deployment.AppendVolumeIfMissing(corev1.Volume{ 260 Name: "shared", 261 VolumeSource: corev1.VolumeSource{ 262 EmptyDir: &corev1.EmptyDirVolumeSource{ 263 Medium: corev1.StorageMediumMemory, 264 }, 265 }, 266 }) 267 268 hsmConfig, err := config.ReadHSMConfig(o.Client, instance) 269 if err != nil { 270 return err 271 } 272 273 hsmSettings(instance, hsmConfig, peerContainer, deployment) 274 275 deployment.UpdateContainer(peerContainer) 276 } 277 278 if version.GetMajorReleaseVersion(instance.Spec.FabricVersion) == version.V2 { 279 err = o.V2Deployment(instance, deployment) 280 if err != nil { 281 return errors.Wrap(err, "failed during V2 peer deployment overrides") 282 } 283 peerVersion := version.String(instance.Spec.FabricVersion) 284 if peerVersion.EqualWithoutTag(version.V2_4_1) || peerVersion.GreaterThan(version.V2_4_1) { 285 err = o.V24Deployment(instance, deployment) 286 if err != nil { 287 return errors.Wrap(err, "failed during V24 peer deployment overrides") 288 } 289 } 290 } else { 291 err = o.V1Deployment(instance, deployment) 292 if err != nil { 293 return errors.Wrap(err, "failed during V1 peer deployment overrides") 294 } 295 } 296 297 return nil 298 } 299 300 func (o *Override) V1Deployment(instance *current.IBPPeer, deployment *dep.Deployment) error { 301 initContainer := deployment.MustGetContainer(INIT) 302 303 // NOTE: The container doesn't like when these bash commands are listed as separate strings in the command field 304 // which is why the command has been formatted into a single string. 305 // 306 // This command checks the permissions, owner, and group of /data/ and runs chmod/chown on required dirs if they 307 // have yet to be set to the default values (775, 1000, and 1000 respectively). 308 // 309 // cmdFormat is a format string that configured with the list of directories when used. 310 cmdFormat := "DEFAULT_PERM=775 && DEFAULT_USER=7051 && DEFAULT_GROUP=1000 " 311 cmdFormat += `&& PERM=$(stat -c "%%a" /data/) && USER=$(stat -c "%%u" /data/) && GROUP=$(stat -c "%%g" /data/) ` // %% is used to escape the percent symbol 312 cmdFormat += `&& if [ ${PERM} != ${DEFAULT_PERM} ] || [ ${USER} != ${DEFAULT_USER} ] || [ ${GROUP} != ${DEFAULT_GROUP} ]; ` 313 cmdFormat += `then chmod -R ${DEFAULT_PERM} %[1]s && chown -R -H ${DEFAULT_USER}:${DEFAULT_GROUP} %[1]s; fi` 314 315 // NOTE: There are two chmod & chown calls for /data/ and /data/peer/... because 316 // those are two separate pvc mounts, so we were running the command for both the locations. 317 if instance.UsingCouchDB() { 318 directories := "/data/" 319 cmd := fmt.Sprintf(cmdFormat, directories) 320 initContainer.SetCommand([]string{ 321 "bash", 322 "-c", 323 cmd, 324 }) 325 } else { 326 directories := "/{data/,data/peer/ledgersData/stateLeveldb}" 327 cmd := fmt.Sprintf(cmdFormat, directories) 328 initContainer.SetCommand([]string{ 329 "bash", 330 "-c", 331 cmd, 332 }) 333 } 334 335 fluentdContainer, err := deployment.GetContainer(FLUENTD) 336 if err != nil { 337 return errors.New("fluentD container not found in deployment") 338 } 339 340 dindContainer, err := deployment.GetContainer(DIND) 341 if err != nil { 342 return errors.New("dind container not found in deployment") 343 } 344 345 dindargs := instance.Spec.DindArgs 346 if dindargs == nil { 347 dindargs = []string{"--log-driver", "fluentd", "--log-opt", "fluentd-address=localhost:9880", "--mtu", "1400"} 348 } 349 dindContainer.SetArgs(dindargs) 350 351 image := instance.Spec.Images 352 if image != nil { 353 dindContainer.SetImage(image.DindImage, image.DindTag) 354 fluentdContainer.SetImage(image.FluentdImage, image.FluentdTag) 355 } 356 357 resourcesRequest := instance.Spec.Resources 358 if resourcesRequest != nil { 359 if resourcesRequest.DinD != nil { 360 err = dindContainer.UpdateResources(resourcesRequest.DinD) 361 if err != nil { 362 return errors.Wrap(err, "resource update for dind failed") 363 } 364 } 365 366 if resourcesRequest.FluentD != nil { 367 err = fluentdContainer.UpdateResources(resourcesRequest.FluentD) 368 if err != nil { 369 return errors.Wrap(err, "resource update for fluentd failed") 370 } 371 } 372 } 373 374 peerContainer := deployment.MustGetContainer(PEER) 375 // env vars only required for 1.x peer 376 peerContainer.AppendEnvIfMissing("CORE_VM_ENDPOINT", "localhost:2375") 377 peerContainer.AppendEnvIfMissing("CORE_CHAINCODE_GOLANG_RUNTIME", "golangruntime:latest") 378 peerContainer.AppendEnvIfMissing("CORE_CHAINCODE_CAR_RUNTIME", "carruntime:latest") 379 peerContainer.AppendEnvIfMissing("CORE_CHAINCODE_JAVA_RUNTIME", "javaruntime:latest") 380 peerContainer.AppendEnvIfMissing("CORE_CHAINCODE_NODE_RUNTIME", "noderuntime:latest") 381 peerContainer.AppendEnvIfMissing("CORE_CHAINCODE_BUILDER", "builder:latest") 382 peerContainer.AppendEnvIfMissing("CORE_CHAINCODE_GOLANG_DYNAMICLINK", "true") 383 peerContainer.AppendEnvIfMissing("CORE_VM_DOCKER_ATTACHSTDOUT", "false") 384 385 deployment.UpdateInitContainer(initContainer) 386 deployment.UpdateContainer(fluentdContainer) 387 deployment.UpdateContainer(dindContainer) 388 deployment.UpdateContainer(peerContainer) 389 return nil 390 } 391 392 func (o *Override) V2Deployment(instance *current.IBPPeer, deployment *dep.Deployment) error { 393 394 initContainer := deployment.MustGetContainer(INIT) 395 peerContainer := deployment.MustGetContainer(PEER) 396 397 // NOTE: The container doesn't like when these bash commands are listed as separate strings in the command field 398 // which is why the command has been formatted into a single string. 399 // 400 // This command checks the permissions, owner, and group of /data/ and runs chmod/chown on required dirs if they 401 // have yet to be set to the default values (775, 1000, and 1000 respectively). 402 // 403 // cmdFormat is a format string that configured with the list of directories when used. 404 cmdFormat := "DEFAULT_PERM=775 && DEFAULT_USER=7051 && DEFAULT_GROUP=1000 " 405 cmdFormat += `&& PERM=$(stat -c "%%a" /data/) && USER=$(stat -c "%%u" /data/) && GROUP=$(stat -c "%%g" /data/) ` // %% is used to escape the percent symbol 406 cmdFormat += `&& if [ ${PERM} != ${DEFAULT_PERM} ] || [ ${USER} != ${DEFAULT_USER} ] || [ ${GROUP} != ${DEFAULT_GROUP} ]; ` 407 cmdFormat += `then chmod -R ${DEFAULT_PERM} %[1]s && chown -R -H ${DEFAULT_USER}:${DEFAULT_GROUP} %[1]s; fi` 408 409 // NOTE: There are multiple chmod & chown calls for /data/ and /data/peer/... and /cclauncher because 410 // those are separate pvc mounts, so we were running the command for all the locations 411 dirs := []string{"data/"} 412 if !instance.UsingCouchDB() { 413 dirs = append(dirs, "data/peer/ledgersData/stateLeveldb") 414 } 415 if instance.UsingCCLauncherImage() { 416 dirs = append(dirs, "cclauncher/") 417 } 418 419 var directories string 420 if len(dirs) > 1 { 421 directories = fmt.Sprintf("/{%s}", strings.Join(dirs, ",")) 422 } else { 423 directories = "/data/" 424 } 425 426 initContainer.SetCommand([]string{ 427 "bash", 428 "-c", 429 fmt.Sprintf(cmdFormat, directories), 430 }) 431 432 if instance.UsingCCLauncherImage() { 433 err := o.CreateCCLauncherContainer(instance, deployment) 434 if err != nil { 435 return errors.Wrap(err, "failed to create chaincode launcher container") 436 } 437 438 volumeMountName := fmt.Sprintf("%s-cclauncher", instance.GetName()) 439 initContainer.AppendVolumeMountIfMissing(volumeMountName, "/cclauncher") 440 peerContainer.AppendVolumeMountIfMissing(volumeMountName, "/cclauncher") 441 442 peerContainer.AppendEnvIfMissing("IBP_BUILDER_SHARED_DIR", "/cclauncher") 443 peerContainer.AppendEnvIfMissing("IBP_BUILDER_ENDPOINT", "127.0.0.1:11111") 444 peerContainer.AppendEnvIfMissing("PEER_NAME", instance.GetName()) 445 446 // Will delete these envs if found, these are not required for v2 447 peerContainer.DeleteEnv("CORE_VM_ENDPOINT") 448 peerContainer.DeleteEnv("CORE_CHAINCODE_GOLANG_RUNTIME") 449 peerContainer.DeleteEnv("CORE_CHAINCODE_CAR_RUNTIME") 450 peerContainer.DeleteEnv("CORE_CHAINCODE_JAVA_RUNTIME") 451 peerContainer.DeleteEnv("CORE_CHAINCODE_NODE_RUNTIME") 452 peerContainer.DeleteEnv("CORE_CHAINCODE_BUILDER") 453 peerContainer.DeleteEnv("CORE_CHAINCODE_GOLANG_DYNAMICLINK") 454 peerContainer.DeleteEnv("CORE_VM_DOCKER_ATTACHSTDOUT") 455 456 deployment.AppendEmptyDirVolumeIfMissing(fmt.Sprintf("%s-cclauncher", instance.Name), corev1.StorageMediumMemory) 457 } 458 459 // Append a k/v JSON substitution map to the peer env. 460 if instance.Spec.ChaincodeBuilderConfig != nil { 461 configJSON, err := json.Marshal(instance.Spec.ChaincodeBuilderConfig) 462 if err != nil { 463 return errors.Wrapf(err, "failed to marshal chaincode builder config '%s',", instance.Spec.ChaincodeBuilderConfig) 464 } 465 peerContainer.AppendEnvIfMissing("CHAINCODE_AS_A_SERVICE_BUILDER_CONFIG", string(configJSON)) 466 } 467 468 deployment.UpdateInitContainer(initContainer) 469 deployment.UpdateContainer(peerContainer) 470 deployment.RemoveContainer(FLUENTD) 471 deployment.RemoveContainer(DIND) 472 return nil 473 } 474 475 func (o *Override) V24Deployment(instance *current.IBPPeer, deployment *dep.Deployment) error { 476 if instance.UsingCCLauncherImage() { 477 launcherContainer := deployment.MustGetContainer(CCLAUNCHER) 478 479 launcherContainer.LivenessProbe.HTTPGet.Scheme = corev1.URISchemeHTTPS 480 launcherContainer.ReadinessProbe.HTTPGet.Scheme = corev1.URISchemeHTTPS 481 deployment.UpdateContainer(launcherContainer) 482 } 483 return nil 484 } 485 486 func (o *Override) V2DeploymentUpdate(instance *current.IBPPeer, deployment *dep.Deployment) error { 487 peerContainer, err := deployment.GetContainer(PEER) 488 if err != nil { 489 return err 490 } 491 peerContainer.AppendEnvIfMissing("PEER_NAME", instance.GetName()) 492 493 // For V2Deployments using chaincode-as-a-service and external builders, there is no need to include 494 // or modify the chaincode launcher sidecar. 495 if !instance.UsingCCLauncherImage() { 496 if err := o.V2Deployment(instance, deployment); err != nil { 497 return err 498 } 499 return nil 500 } 501 502 // V2DeploymentUpdate will be triggered when migrating from v1 to v2 peer, during this update we might 503 // have to run initialization logic for a v2 fabric deployment. If the chaincode launcher container is 504 // not found, we try to initialize the deployment based on v2 deployment to add chaincode launcher 505 // before continuing with the remaining update logic. Not ideal, but until a bigger refactor is performed 506 // this is the least intrusive way to handle this. 507 ccLauncherContainer, err := deployment.GetContainer(CCLAUNCHER) 508 if err != nil { 509 if err := o.V2Deployment(instance, deployment); err != nil { 510 return err 511 } 512 return nil 513 } 514 515 ccLauncherContainer = deployment.MustGetContainer(CCLAUNCHER) 516 images := instance.Spec.Images 517 if images != nil { 518 if images.CCLauncherImage != "" && images.CCLauncherTag != "" { 519 ccLauncherContainer.SetImage(images.CCLauncherImage, images.CCLauncherTag) 520 } 521 522 ccLauncherContainer.AppendEnvIfMissingOverrideIfPresent( 523 "FILETRANSFERIMAGE", image.Format(instance.Spec.Images.PeerInitImage, instance.Spec.Images.PeerInitTag), 524 ) 525 ccLauncherContainer.AppendEnvIfMissingOverrideIfPresent( 526 "BUILDERIMAGE", image.Format(instance.Spec.Images.BuilderImage, instance.Spec.Images.BuilderTag), 527 ) 528 ccLauncherContainer.AppendEnvIfMissingOverrideIfPresent( 529 "GOENVIMAGE", image.Format(instance.Spec.Images.GoEnvImage, instance.Spec.Images.GoEnvTag), 530 ) 531 ccLauncherContainer.AppendEnvIfMissingOverrideIfPresent( 532 "JAVAENVIMAGE", image.Format(instance.Spec.Images.JavaEnvImage, instance.Spec.Images.JavaEnvTag), 533 ) 534 ccLauncherContainer.AppendEnvIfMissingOverrideIfPresent( 535 "NODEENVIMAGE", image.Format(instance.Spec.Images.NodeEnvImage, instance.Spec.Images.NodeEnvTag), 536 ) 537 ccLauncherContainer.AppendEnvIfMissing("CORE_PEER_LOCALMSPID", instance.Spec.MSPID) 538 } 539 540 resourcesRequest := instance.Spec.Resources 541 if resourcesRequest != nil { 542 if resourcesRequest.CCLauncher != nil { 543 err := ccLauncherContainer.UpdateResources(resourcesRequest.CCLauncher) 544 if err != nil { 545 return errors.Wrap(err, "resource update for cclauncher failed") 546 } 547 } 548 } 549 550 return nil 551 } 552 553 func (o *Override) V24DeploymentUpdate(instance *current.IBPPeer, deployment *dep.Deployment) error { 554 if instance.UsingCCLauncherImage() { 555 ccLauncherContainer, err := deployment.GetContainer(CCLAUNCHER) 556 if err != nil { 557 return err 558 } 559 ccLauncherContainer.LivenessProbe.HTTPGet.Scheme = corev1.URISchemeHTTPS 560 ccLauncherContainer.ReadinessProbe.HTTPGet.Scheme = corev1.URISchemeHTTPS 561 562 deployment.UpdateContainer(ccLauncherContainer) 563 } 564 return nil 565 } 566 567 func (o *Override) CreateCCLauncherContainer(instance *current.IBPPeer, deployment *dep.Deployment) error { 568 ccLauncherContainer, err := container.LoadFromFile(o.DefaultCCLauncherFile) 569 if err != nil { 570 return errors.Wrap(err, "failed to read default chaincode launcher container file") 571 } 572 573 images := instance.Spec.Images 574 if images == nil || images.CCLauncherImage == "" { 575 return errors.New("no image specified for chaincode launcher") 576 } 577 578 ccLauncherContainer.SetImage(images.CCLauncherImage, images.CCLauncherTag) 579 ccLauncherContainer.AppendEnvIfMissing("KUBE_NAMESPACE", instance.GetNamespace()) 580 ccLauncherContainer.AppendEnvIfMissing("SHARED_VOLUME_PATH", "/cclauncher") 581 ccLauncherContainer.AppendEnvIfMissing("IMAGEPULLSECRETS", strings.Join(instance.Spec.ImagePullSecrets, " ")) 582 ccLauncherContainer.AppendEnvIfMissing("CORE_PEER_LOCALMSPID", instance.Spec.MSPID) 583 584 valueFrom := &corev1.EnvVarSource{ 585 FieldRef: &corev1.ObjectFieldSelector{ 586 FieldPath: "metadata.name", 587 }, 588 } 589 ccLauncherContainer.AppendEnvVarValueFromIfMissing("PEER_POD_NAME", valueFrom) 590 591 valueFrom = &corev1.EnvVarSource{ 592 FieldRef: &corev1.ObjectFieldSelector{ 593 FieldPath: "metadata.uid", 594 }, 595 } 596 ccLauncherContainer.AppendEnvVarValueFromIfMissing("PEER_POD_UID", valueFrom) 597 598 ccLauncherContainer.AppendEnvIfMissingOverrideIfPresent("FILETRANSFERIMAGE", image.Format(instance.Spec.Images.PeerInitImage, instance.Spec.Images.PeerInitTag)) 599 ccLauncherContainer.AppendEnvIfMissingOverrideIfPresent("BUILDERIMAGE", image.Format(instance.Spec.Images.BuilderImage, instance.Spec.Images.BuilderTag)) 600 ccLauncherContainer.AppendEnvIfMissingOverrideIfPresent("GOENVIMAGE", image.Format(instance.Spec.Images.GoEnvImage, instance.Spec.Images.GoEnvTag)) 601 ccLauncherContainer.AppendEnvIfMissingOverrideIfPresent("JAVAENVIMAGE", image.Format(instance.Spec.Images.JavaEnvImage, instance.Spec.Images.JavaEnvTag)) 602 ccLauncherContainer.AppendEnvIfMissingOverrideIfPresent("NODEENVIMAGE", image.Format(instance.Spec.Images.NodeEnvImage, instance.Spec.Images.NodeEnvTag)) 603 ccLauncherContainer.AppendEnvIfMissingOverrideIfPresent("PEER_ID", instance.GetName()) 604 ccLauncherContainer.AppendVolumeMountIfMissing(fmt.Sprintf("%s-cclauncher", instance.Name), "/cclauncher") 605 606 resourcesRequest := instance.Spec.Resources 607 if resourcesRequest != nil { 608 if resourcesRequest.CCLauncher != nil { 609 err = ccLauncherContainer.UpdateResources(resourcesRequest.CCLauncher) 610 if err != nil { 611 return errors.Wrap(err, "resource update for cclauncher failed") 612 } 613 } 614 } 615 616 deployment.AddContainer(*ccLauncherContainer) 617 return nil 618 } 619 620 func (o *Override) UpdateDeployment(instance *current.IBPPeer, k8sDep *appsv1.Deployment) error { 621 deployment := dep.New(k8sDep) 622 err := o.CommonDeploymentOverrides(instance, deployment) 623 if err != nil { 624 return err 625 } 626 627 switch version.GetMajorReleaseVersion(instance.Spec.FabricVersion) { 628 case version.V1: 629 err = o.V1Deployment(instance, deployment) 630 if err != nil { 631 return errors.Wrap(err, "failed during V1 peer deployment overrides") 632 } 633 case version.V2: 634 err := o.V2DeploymentUpdate(instance, deployment) 635 if err != nil { 636 return errors.Wrapf(err, "failed to update V2 fabric deployment for instance '%s'", instance.GetName()) 637 } 638 peerVersion := version.String(instance.Spec.FabricVersion) 639 if peerVersion.EqualWithoutTag(version.V2_4_1) || peerVersion.GreaterThan(version.V2_4_1) { 640 err := o.V24DeploymentUpdate(instance, deployment) 641 if err != nil { 642 return errors.Wrapf(err, "failed to update V24 fabric deployment for instance '%s'", instance.GetName()) 643 } 644 } 645 } 646 647 if instance.UsingCouchDB() { 648 couchdb := deployment.MustGetContainer(COUCHDB) 649 650 image := instance.Spec.Images 651 if image != nil { 652 couchdb.SetImage(image.CouchDBImage, image.CouchDBTag) 653 } 654 655 couchdb.AppendEnvIfMissing("SKIP_PERMISSIONS_UPDATE", "true") 656 } 657 658 if instance.UsingHSMProxy() { 659 peerContainer := deployment.MustGetContainer(PEER) 660 peerContainer.UpdateEnv("PKCS11_PROXY_SOCKET", instance.Spec.HSM.PKCS11Endpoint) 661 deployment.UpdateContainer(peerContainer) 662 } else if instance.IsHSMEnabled() { 663 hsmInitCont := deployment.MustGetContainer(HSMCLIENT) 664 image := instance.Spec.Images 665 if image != nil { 666 hsmInitCont.SetImage(image.HSMImage, image.HSMTag) 667 } 668 } 669 670 return nil 671 } 672 673 func (o *Override) CommonDeploymentOverrides(instance *current.IBPPeer, deployment *dep.Deployment) error { 674 initContainer := deployment.MustGetContainer(INIT) 675 peerContainer := deployment.MustGetContainer(PEER) 676 grpcContainer, err := deployment.GetContainer(PROXY) 677 if err != nil { 678 return errors.New("proxy container not found in deployment spec") 679 } 680 681 image := instance.Spec.Images 682 if image != nil { 683 initContainer.SetImage(image.PeerInitImage, image.PeerInitTag) 684 peerContainer.SetImage(image.PeerImage, image.PeerTag) 685 grpcContainer.SetImage(image.GRPCWebImage, image.GRPCWebTag) 686 687 if instance.UsingCouchDB() { 688 couchdb := deployment.MustGetContainer(COUCHDB) 689 couchdb.SetImage(image.CouchDBImage, image.CouchDBTag) 690 691 couchdbInitContainer := deployment.MustGetContainer(COUCHDBINIT) 692 couchdbInitContainer.SetImage(image.PeerInitImage, image.PeerInitTag) 693 } 694 } 695 696 resourcesRequest := instance.Spec.Resources 697 if resourcesRequest != nil { 698 if resourcesRequest.Peer != nil { 699 err = peerContainer.UpdateResources(resourcesRequest.Peer) 700 if err != nil { 701 return errors.Wrap(err, "resource update for peer failed") 702 } 703 } 704 705 if resourcesRequest.GRPCProxy != nil { 706 err = grpcContainer.UpdateResources(resourcesRequest.GRPCProxy) 707 if err != nil { 708 return errors.Wrap(err, "resource update for grpcproxy failed") 709 } 710 } 711 712 if instance.UsingCouchDB() { 713 couchdb := deployment.MustGetContainer(COUCHDB) 714 if resourcesRequest.CouchDB != nil { 715 err = couchdb.UpdateResources(resourcesRequest.CouchDB) 716 if err != nil { 717 return errors.Wrap(err, "resource update for couchdb failed") 718 } 719 } 720 721 couchdbinit := deployment.MustGetContainer(COUCHDBINIT) 722 if resourcesRequest.Init != nil { 723 err = couchdbinit.UpdateResources(resourcesRequest.Init) 724 if err != nil { 725 return errors.Wrap(err, "resource update for couchdb init failed") 726 } 727 } 728 } 729 } 730 731 externalAddress := instance.Spec.PeerExternalEndpoint 732 // Set external address to "do-not-set" in Peer CR spec to disable Service discovery 733 if externalAddress != "" && externalAddress != "do-not-set" { 734 peerContainer.AppendEnvIfMissing("CORE_PEER_GOSSIP_EXTERNALENDPOINT", externalAddress) 735 peerContainer.AppendEnvIfMissing("CORE_PEER_GOSSIP_ENDPOINT", externalAddress) 736 grpcContainer.AppendEnvIfMissing("EXTERNAL_ADDRESS", externalAddress) 737 } 738 739 if instance.Spec.Replicas != nil { 740 if *instance.Spec.Replicas > 1 { 741 return errors.New("replicas > 1 not allowed in IBPPeer") 742 } 743 deployment.SetReplicas(instance.Spec.Replicas) 744 } 745 746 deployment.UpdateContainer(peerContainer) 747 deployment.UpdateContainer(grpcContainer) 748 return nil 749 } 750 751 func (o *Override) CreateCouchDBContainers(instance *current.IBPPeer, deployment *dep.Deployment) error { 752 couchdbUser := o.CouchdbUser 753 if couchdbUser == "" { 754 couchdbUser = util.GenerateRandomString(32) 755 } 756 757 couchdbPassword := o.CouchdbPassword 758 if couchdbPassword == "" { 759 couchdbPassword = util.GenerateRandomString(32) 760 } 761 762 couchdbContainer, err := container.LoadFromFile(o.DefaultCouchContainerFile) 763 if err != nil { 764 return errors.Wrap(err, "failed to read default couch container file") 765 } 766 767 couchdbInitContainer, err := container.LoadFromFile(o.DefaultCouchInitContainerFile) 768 if err != nil { 769 return errors.Wrap(err, "failed to read default couch init container file") 770 } 771 772 image := instance.Spec.Images 773 if image != nil { 774 couchdbContainer.SetImage(image.CouchDBImage, image.CouchDBTag) 775 couchdbInitContainer.SetImage(image.PeerInitImage, image.PeerInitTag) 776 } 777 778 couchdbContainer.AppendEnvIfMissing("COUCHDB_USER", couchdbUser) 779 couchdbContainer.AppendEnvIfMissing("COUCHDB_PASSWORD", couchdbPassword) 780 couchdbContainer.AppendEnvIfMissing("SKIP_PERMISSIONS_UPDATE", "true") 781 782 peerContainer := deployment.MustGetContainer(PEER) 783 peerContainer.AppendEnvIfMissing("CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME", couchdbUser) 784 peerContainer.AppendEnvIfMissing("CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD", couchdbPassword) 785 peerContainer.AppendEnvIfMissing("CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS", "localhost:5984") 786 peerContainer.AppendEnvIfMissing("CORE_LEDGER_STATE_COUCHDBCONFIG_MAXRETRIESONSTARTUP", "20") 787 788 deployment.AddContainer(*couchdbContainer) 789 deployment.AddInitContainer(*couchdbInitContainer) 790 deployment.UpdateContainer(peerContainer) 791 792 return nil 793 } 794 795 func (o *Override) GetAffinity(instance *current.IBPPeer) *corev1.Affinity { 796 arch := instance.Spec.Arch 797 zone := instance.Spec.Zone 798 region := instance.Spec.Region 799 nodeSelectorTerms := common.GetNodeSelectorTerms(arch, zone, region) 800 801 orgName := instance.Spec.MSPID 802 podAntiAffinity := common.GetPodAntiAffinity(orgName) 803 804 affinity := &corev1.Affinity{ 805 PodAntiAffinity: podAntiAffinity, 806 } 807 808 if len(nodeSelectorTerms[0].MatchExpressions) != 0 { 809 affinity.NodeAffinity = &corev1.NodeAffinity{ 810 RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ 811 NodeSelectorTerms: nodeSelectorTerms, 812 }, 813 } 814 } 815 816 return affinity 817 } 818 819 func (o *Override) AdminSecretExists(instance *current.IBPPeer) bool { 820 secret := &corev1.Secret{} 821 err := o.Client.Get(context.TODO(), types.NamespacedName{ 822 Name: fmt.Sprintf("ecert-%s-admincerts", instance.Name), 823 Namespace: instance.Namespace}, secret) 824 if err != nil { 825 return false 826 } 827 828 return true 829 } 830 831 func (o *Override) OrdererCACertsSecretExists(instance *current.IBPPeer) bool { 832 err := o.Client.Get(context.TODO(), types.NamespacedName{ 833 Name: fmt.Sprintf("%s-orderercacerts", instance.Name), 834 Namespace: instance.Namespace}, &corev1.Secret{}) 835 if err != nil { 836 return false 837 } 838 839 return true 840 } 841 842 func (o *Override) GetTLSCACertsSecret(instance *current.IBPPeer, secretName string) (*corev1.Secret, error) { 843 secret := &corev1.Secret{} 844 err := o.Client.Get(context.TODO(), types.NamespacedName{ 845 Name: secretName, 846 Namespace: instance.Namespace}, secret) 847 if err != nil { 848 } 849 850 return secret, nil 851 } 852 853 func hsmInitContainer(instance *current.IBPPeer, hsmConfig *config.HSMConfig) *container.Container { 854 hsmLibraryPath := hsmConfig.Library.FilePath 855 hsmLibraryName := filepath.Base(hsmLibraryPath) 856 857 f := false 858 user := int64(0) 859 mountPath := "/shared" 860 return &container.Container{ 861 Container: &corev1.Container{ 862 Name: "hsm-client", 863 Image: fmt.Sprintf("%s:%s", instance.Spec.Images.HSMImage, instance.Spec.Images.HSMTag), 864 ImagePullPolicy: corev1.PullAlways, 865 Command: []string{ 866 "sh", 867 "-c", 868 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), 869 }, 870 SecurityContext: &corev1.SecurityContext{ 871 RunAsUser: &user, 872 RunAsNonRoot: &f, 873 }, 874 VolumeMounts: []corev1.VolumeMount{ 875 corev1.VolumeMount{ 876 Name: "shared", 877 MountPath: mountPath, 878 }, 879 }, 880 Resources: corev1.ResourceRequirements{ 881 Requests: corev1.ResourceList{ 882 corev1.ResourceCPU: resource.MustParse("0.1"), 883 corev1.ResourceMemory: resource.MustParse("100Mi"), 884 }, 885 Limits: corev1.ResourceList{ 886 corev1.ResourceCPU: resource.MustParse("2"), 887 corev1.ResourceMemory: resource.MustParse("4Gi"), 888 }, 889 }, 890 }, 891 } 892 } 893 894 func hsmSettings(instance *current.IBPPeer, hsmConfig *config.HSMConfig, peerCont container.Container, deployment *deployment.Deployment) { 895 for _, v := range hsmConfig.GetVolumes() { 896 deployment.AppendVolumeIfMissing(v) 897 } 898 899 for _, vm := range hsmConfig.GetVolumeMounts() { 900 peerCont.AppendVolumeMountStructIfMissing(vm) 901 } 902 903 for _, env := range hsmConfig.GetEnvs() { 904 peerCont.AppendEnvStructIfMissing(env) 905 } 906 907 peerCont.AppendVolumeMountWithSubPathIfMissing("shared", "/hsm/lib", "hsm") 908 909 if hsmConfig.Library.Auth != nil { 910 deployment.Spec.Template.Spec.ImagePullSecrets = util.AppendPullSecretIfMissing( 911 deployment.Spec.Template.Spec.ImagePullSecrets, 912 hsmConfig.Library.Auth.ImagePullSecret, 913 ) 914 } 915 916 deployment.AddInitContainer(*hsmInitContainer(instance, hsmConfig)) 917 918 // If daemon settings are configured in HSM config, create a sidecar that is running the daemon image 919 if hsmConfig.Daemon != nil { 920 hsmDaemonSettings(instance, hsmConfig, peerCont, deployment) 921 } 922 } 923 924 func hsmDaemonSettings(instance *current.IBPPeer, hsmConfig *config.HSMConfig, peerCont container.Container, deployment *deployment.Deployment) { 925 // Unable to launch daemon if not running priviledged moe 926 t := true 927 peerCont.SecurityContext.Privileged = &t 928 peerCont.SecurityContext.AllowPrivilegeEscalation = &t 929 930 // Update command in deployment to ensure that deamon is running before starting the ca 931 peerCont.Command = []string{ 932 "sh", 933 "-c", 934 fmt.Sprintf("%s && %s", config.DAEMON_CHECK_CMD, "peer node start"), 935 } 936 937 // This is the shared volume where the file 'pkcsslotd-luanched' is touched to let 938 // other containers know that the daemon has successfully launched. 939 peerCont.AppendVolumeMountIfMissing("shared", "/shared") 940 941 pvcVolumeName := "fabric-peer-0" 942 // Certain token information requires to be stored in persistent store, the administrator 943 // responsible for configuring HSM sets the HSM config to point to the path where the PVC 944 // needs to be mounted. 945 var pvcMount *corev1.VolumeMount 946 for _, vm := range hsmConfig.MountPaths { 947 if vm.UsePVC { 948 pvcMount = &corev1.VolumeMount{ 949 Name: pvcVolumeName, 950 MountPath: vm.MountPath, 951 } 952 } 953 } 954 955 // If a pull secret is required to pull daemon image, update the deployment's image pull secrets 956 if hsmConfig.Daemon.Auth != nil { 957 deployment.Spec.Template.Spec.ImagePullSecrets = util.AppendPullSecretIfMissing( 958 deployment.Spec.Template.Spec.ImagePullSecrets, 959 hsmConfig.Daemon.Auth.ImagePullSecret, 960 ) 961 } 962 963 // Add daemon container to the deployment 964 config.AddDaemonContainer(hsmConfig, deployment, instance.GetResource(current.HSMDAEMON), pvcMount) 965 966 // If a pvc mount has been configured in HSM config, set the volume mount on the ca container 967 // and PVC volume to deployment if missing 968 if pvcMount != nil { 969 peerCont.AppendVolumeMountStructIfMissing(*pvcMount) 970 deployment.AppendPVCVolumeIfMissing(pvcVolumeName, instance.PVCName()) 971 } 972 }