github.com/IBM-Blockchain/fabric-operator@v1.0.4/pkg/offering/base/console/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 "encoding/json" 23 "net/url" 24 25 "github.com/pkg/errors" 26 27 current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1" 28 defaultconsole "github.com/IBM-Blockchain/fabric-operator/defaultconfig/console" 29 deployerimgs "github.com/IBM-Blockchain/fabric-operator/pkg/apis/deployer" 30 "github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources" 31 "github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources/container" 32 dep "github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources/deployment" 33 "github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources/serviceaccount" 34 "github.com/IBM-Blockchain/fabric-operator/pkg/offering/common" 35 "github.com/IBM-Blockchain/fabric-operator/pkg/util/image" 36 appsv1 "k8s.io/api/apps/v1" 37 corev1 "k8s.io/api/core/v1" 38 "k8s.io/apimachinery/pkg/api/resource" 39 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 40 "k8s.io/apimachinery/pkg/util/intstr" 41 ) 42 43 // Container names 44 const ( 45 INIT = "init" 46 CONSOLE = "optools" 47 DEPLOYER = "deployer" 48 CONFIGTXLATOR = "configtxlator" 49 COUCHDB = "couchdb" 50 ) 51 52 func (o *Override) Deployment(object v1.Object, deployment *appsv1.Deployment, action resources.Action) error { 53 instance := object.(*current.IBPConsole) 54 switch action { 55 case resources.Create: 56 return o.CreateDeployment(instance, deployment) 57 case resources.Update: 58 return o.UpdateDeployment(instance, deployment) 59 } 60 61 return nil 62 } 63 64 func (o *Override) CreateDeployment(instance *current.IBPConsole, k8sDep *appsv1.Deployment) error { 65 deployment := dep.New(k8sDep) 66 67 name := instance.GetName() 68 deployment.SetServiceAccountName(serviceaccount.GetName(name)) 69 70 // Make sure containers exist 71 console, err := deployment.GetContainer(CONSOLE) 72 if err != nil { 73 return errors.New("console container not found in deployment spec") 74 } 75 _, err = deployment.GetContainer(INIT) 76 if err != nil { 77 return errors.New("init container not found in deployment spec") 78 } 79 _, err = deployment.GetContainer(DEPLOYER) 80 if err != nil { 81 return errors.New("deployer container not found in deployment spec") 82 } 83 _, err = deployment.GetContainer(CONFIGTXLATOR) 84 if err != nil { 85 return errors.New("configtxlator container not found in deployment spec") 86 } 87 88 if !instance.Spec.UsingRemoteDB() { 89 couchdb := o.CreateCouchdbContainer() 90 91 couchdb.AppendVolumeMountWithSubPathIfMissing("couchdb", "/opt/couchdb/data", "data") 92 deployment.AddContainer(couchdb) 93 } 94 95 err = o.CommonDeployment(instance, deployment) 96 if err != nil { 97 return err 98 } 99 100 deployment.SetImagePullSecrets(instance.Spec.ImagePullSecrets) 101 102 console.AppendConfigMapFromSourceIfMissing(name) 103 104 passwordSecretName := instance.Spec.PasswordSecretName 105 valueFrom := &corev1.EnvVarSource{ 106 SecretKeyRef: &corev1.SecretKeySelector{ 107 LocalObjectReference: corev1.LocalObjectReference{ 108 Name: passwordSecretName, 109 }, 110 Key: "password", 111 }, 112 } 113 console.AppendEnvVarValueFromIfMissing("DEFAULT_USER_PASSWORD_INITIAL", valueFrom) 114 115 tlsSecretName := instance.Spec.TLSSecretName 116 if tlsSecretName != "" { 117 console.AppendVolumeMountIfMissing("tls-certs", "/certs/tls") 118 deployment.AppendSecretVolumeIfMissing("tls-certs", tlsSecretName) 119 } else { 120 // TODO: generate and create the TLS Secret here itself 121 } 122 123 if !instance.Spec.UsingRemoteDB() { 124 deployment.AppendPVCVolumeIfMissing("couchdb", instance.Name+"-pvc") 125 } 126 127 deployment.AppendConfigMapVolumeIfMissing("deployer-template", name+"-deployer") 128 deployment.AppendConfigMapVolumeIfMissing("template", name+"-console") 129 deployment.SetAffinity(o.GetAffinity(instance)) 130 131 return nil 132 } 133 134 func (o *Override) UpdateDeployment(instance *current.IBPConsole, k8sDep *appsv1.Deployment) error { 135 deployment := dep.New(k8sDep) 136 return o.CommonDeployment(instance, deployment) 137 } 138 139 func (o *Override) CommonDeployment(instance *current.IBPConsole, deployment *dep.Deployment) error { 140 init := deployment.MustGetContainer(INIT) 141 console := deployment.MustGetContainer(CONSOLE) 142 deployer := deployment.MustGetContainer(DEPLOYER) 143 configtxlator := deployment.MustGetContainer(CONFIGTXLATOR) 144 145 registryURL := instance.Spec.RegistryURL 146 arch := "amd64" 147 if instance.Spec.Arch != nil { 148 arch = instance.Spec.Arch[0] 149 } 150 151 images := &deployerimgs.ConsoleImages{} 152 if instance.Spec.Images != nil { 153 // convert spec images to deployer config images 154 instanceImgBytes, err := json.Marshal(instance.Spec.Images) 155 if err != nil { 156 return err 157 } 158 err = json.Unmarshal(instanceImgBytes, images) 159 if err != nil { 160 return err 161 } 162 } 163 164 var consoleImage, consoleTag, initImage, initTag, deployerImage, deployerTag string 165 var configtxlatorImage, configtxlatorTag, couchdbImage, couchdbTag string 166 167 defaultimage := defaultconsole.GetImages() 168 consoleImage = image.GetImage(registryURL, defaultimage.ConsoleImage, images.ConsoleImage) 169 initImage = image.GetImage(registryURL, defaultimage.ConsoleInitImage, images.ConsoleInitImage) 170 deployerImage = image.GetImage(registryURL, defaultimage.DeployerImage, images.DeployerImage) 171 configtxlatorImage = image.GetImage(registryURL, defaultimage.ConfigtxlatorImage, images.ConfigtxlatorImage) 172 173 if instance.UseTags() { 174 consoleTag = image.GetTag(arch, defaultimage.ConsoleTag, images.ConsoleTag) 175 initTag = image.GetTag(arch, defaultimage.ConsoleInitTag, images.ConsoleInitTag) 176 deployerTag = image.GetTag(arch, defaultimage.DeployerTag, images.DeployerTag) 177 configtxlatorTag = image.GetTag(arch, defaultimage.ConfigtxlatorTag, images.ConfigtxlatorTag) 178 } else { 179 consoleTag = image.GetTag(arch, defaultimage.ConsoleDigest, images.ConsoleDigest) 180 initTag = image.GetTag(arch, defaultimage.ConsoleInitDigest, images.ConsoleInitDigest) 181 deployerTag = image.GetTag(arch, defaultimage.DeployerDigest, images.DeployerDigest) 182 configtxlatorTag = image.GetTag(arch, defaultimage.ConfigtxlatorDigest, images.ConfigtxlatorDigest) 183 } 184 init.SetImage(initImage, initTag) 185 console.SetImage(consoleImage, consoleTag) 186 deployer.SetImage(deployerImage, deployerTag) 187 configtxlator.SetImage(configtxlatorImage, configtxlatorTag) 188 189 resourcesRequest := instance.Spec.Resources 190 if !instance.Spec.UsingRemoteDB() { 191 couchdb := deployment.MustGetContainer(COUCHDB) 192 193 if instance.Spec.ConnectionString != "" { 194 connectionURL, err := url.Parse(instance.Spec.ConnectionString) 195 if err != nil { 196 return err 197 } 198 if connectionURL.Host == "localhost:5984" { 199 if connectionURL.Scheme == "http" { 200 couchdbUser := connectionURL.User.Username() 201 couchdbPassword, set := connectionURL.User.Password() 202 if set { 203 couchdb.AppendEnvIfMissing("COUCHDB_USER", couchdbUser) 204 couchdb.AppendEnvIfMissing("COUCHDB_PASSWORD", couchdbPassword) 205 } 206 } 207 } 208 } 209 210 couchdbImage = image.GetImage(registryURL, defaultimage.CouchDBImage, images.CouchDBImage) 211 if instance.Spec.UseTags != nil && *(instance.Spec.UseTags) { 212 couchdbTag = image.GetTag(arch, defaultimage.CouchDBTag, images.CouchDBTag) 213 } else { 214 couchdbTag = image.GetTag(arch, defaultimage.CouchDBDigest, images.CouchDBDigest) 215 216 } 217 couchdb.SetImage(couchdbImage, couchdbTag) 218 219 if resourcesRequest != nil { 220 if resourcesRequest.CouchDB != nil { 221 err := couchdb.UpdateResources(resourcesRequest.CouchDB) 222 if err != nil { 223 return errors.Wrap(err, "update resources for couchdb failed") 224 } 225 } 226 } 227 } 228 229 if resourcesRequest != nil { 230 if resourcesRequest.Console != nil { 231 err := console.UpdateResources(resourcesRequest.Console) 232 if err != nil { 233 return errors.Wrap(err, "update resources for console failed") 234 } 235 } 236 237 if resourcesRequest.Deployer != nil { 238 err := deployer.UpdateResources(resourcesRequest.Deployer) 239 if err != nil { 240 return errors.Wrap(err, "update resources for deployer failed") 241 } 242 } 243 244 if resourcesRequest.Configtxlator != nil { 245 err := configtxlator.UpdateResources(resourcesRequest.Configtxlator) 246 if err != nil { 247 return errors.Wrap(err, "update resources for configtxlator failed") 248 } 249 } 250 } 251 252 if err := setReplicas(instance, deployment); err != nil { 253 return err 254 } 255 setDeploymentStrategy(instance, deployment) 256 setSpreadConstraints(instance, deployment) 257 258 kubeconfigSecretName := instance.Spec.KubeconfigSecretName 259 if kubeconfigSecretName != "" { 260 deployer.AppendVolumeMountIfMissing("kubeconfig", "/kubeconfig/") 261 deployment.AppendSecretVolumeIfMissing("kubeconfig", kubeconfigSecretName) 262 deployer.AppendEnvIfMissing("KUBECONFIGPATH", "/kubeconfig/kubeconfig.yaml") 263 } 264 265 kubeconfigNamespace := instance.Spec.KubeconfigNamespace 266 if kubeconfigNamespace != "" { 267 deployer.AppendEnvIfMissing("DEPLOY_NAMESPACE", kubeconfigNamespace) 268 } else { 269 valueFrom := &corev1.EnvVarSource{ 270 FieldRef: &corev1.ObjectFieldSelector{ 271 FieldPath: "metadata.namespace", 272 }, 273 } 274 deployer.AppendEnvVarValueFromIfMissing("DEPLOY_NAMESPACE", valueFrom) 275 } 276 277 consoleOverrides, err := instance.Spec.GetOverridesConsole() 278 if err != nil { 279 return err 280 } 281 282 initCommand := "" 283 if !instance.Spec.UsingRemoteDB() { 284 initCommand = "chmod -R 775 /opt/couchdb/data/ && chown -R -H 5984:5984 /opt/couchdb/data/ && chmod -R 775 /certs/ && chown -R -H 1000:1000 /certs/" 285 286 couchDBVolumeMount := corev1.VolumeMount{ 287 Name: "couchdb", 288 MountPath: "/opt/couchdb/data", 289 SubPath: "data", 290 } 291 292 certsVolumeMount := corev1.VolumeMount{ 293 Name: "couchdb", 294 MountPath: "/certs/", 295 SubPath: "tls", 296 } 297 init.SetVolumeMounts([]corev1.VolumeMount{couchDBVolumeMount, certsVolumeMount}) 298 console.AppendVolumeMountWithSubPathIfMissing("couchdb", "/certs/", "tls") 299 } 300 301 if consoleOverrides.ActivityTrackerConsolePath != "" { 302 hostPath := "/var/log/at" 303 if consoleOverrides.ActivityTrackerHostPath != "" { 304 hostPath = consoleOverrides.ActivityTrackerHostPath 305 } 306 deployment.AppendHostPathVolumeIfMissing("activity", hostPath, corev1.HostPathDirectoryOrCreate) 307 308 console.AppendVolumeMountWithSubPathIfMissing("activity", consoleOverrides.ActivityTrackerConsolePath, instance.Namespace) 309 init.AppendVolumeMountWithSubPathIfMissing("activity", consoleOverrides.ActivityTrackerConsolePath, instance.Namespace) 310 311 if initCommand != "" { 312 initCommand += " && " 313 } 314 initCommand += "chmod -R 775 " + consoleOverrides.ActivityTrackerConsolePath + " && chown -R -H 1000:1000 " + consoleOverrides.ActivityTrackerConsolePath 315 } 316 317 if initCommand == "" { 318 initCommand = "exit 0" 319 } 320 init.SetCommand([]string{"sh", "-c", initCommand}) 321 322 return nil 323 } 324 325 func (o *Override) GetAffinity(instance *current.IBPConsole) *corev1.Affinity { 326 arch := instance.Spec.Arch 327 zone := instance.Spec.Zone 328 region := instance.Spec.Region 329 nodeSelectorTerms := common.GetNodeSelectorTerms(arch, zone, region) 330 331 affinity := &corev1.Affinity{} 332 333 if len(nodeSelectorTerms[0].MatchExpressions) != 0 { 334 affinity.NodeAffinity = &corev1.NodeAffinity{ 335 RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ 336 NodeSelectorTerms: nodeSelectorTerms, 337 }, 338 } 339 } 340 341 return affinity 342 } 343 344 func (o *Override) CreateCouchdbContainer() container.Container { 345 falsep := false 346 truep := true 347 portp := int64(5984) 348 349 couchdb := &corev1.Container{ 350 Name: "couchdb", 351 Image: "", 352 ImagePullPolicy: "Always", 353 Env: []corev1.EnvVar{ 354 corev1.EnvVar{ 355 Name: "LICENSE", 356 Value: "accept", 357 }, 358 }, 359 SecurityContext: &corev1.SecurityContext{ 360 Privileged: &falsep, 361 AllowPrivilegeEscalation: &falsep, 362 ReadOnlyRootFilesystem: &falsep, 363 RunAsNonRoot: &truep, 364 RunAsUser: &portp, 365 Capabilities: &corev1.Capabilities{ 366 Drop: []corev1.Capability{"ALL"}, 367 Add: []corev1.Capability{"NET_BIND_SERVICE", "CHOWN", "DAC_OVERRIDE", "SETGID", "SETUID"}, 368 }, 369 }, 370 Ports: []corev1.ContainerPort{ 371 corev1.ContainerPort{ 372 Name: "http", 373 ContainerPort: 5984, 374 }, 375 }, 376 LivenessProbe: &corev1.Probe{ 377 Handler: corev1.Handler{ 378 TCPSocket: &corev1.TCPSocketAction{ 379 Port: intstr.FromInt(5984), 380 }, 381 }, 382 InitialDelaySeconds: 16, 383 TimeoutSeconds: 5, 384 FailureThreshold: 5, 385 }, 386 ReadinessProbe: &corev1.Probe{ 387 Handler: corev1.Handler{ 388 TCPSocket: &corev1.TCPSocketAction{ 389 Port: intstr.FromInt(5984), 390 }, 391 }, 392 InitialDelaySeconds: 10, 393 TimeoutSeconds: 5, 394 FailureThreshold: 5, 395 }, 396 Resources: corev1.ResourceRequirements{ 397 Limits: corev1.ResourceList{ 398 corev1.ResourceCPU: resource.MustParse("500m"), 399 corev1.ResourceMemory: resource.MustParse("1000Mi"), 400 corev1.ResourceEphemeralStorage: resource.MustParse("1Gi"), 401 }, 402 Requests: corev1.ResourceList{ 403 corev1.ResourceCPU: resource.MustParse("500m"), 404 corev1.ResourceMemory: resource.MustParse("1000Mi"), 405 corev1.ResourceEphemeralStorage: resource.MustParse("100Mi"), 406 }, 407 }, 408 } 409 410 return *container.New(couchdb) 411 } 412 413 func setReplicas(instance *current.IBPConsole, d *dep.Deployment) error { 414 if instance.Spec.Replicas != nil { 415 if !instance.Spec.UsingRemoteDB() && *instance.Spec.Replicas > 1 { 416 return errors.New("replicas > 1 not allowed in IBPConsole") 417 } 418 419 d.SetReplicas(instance.Spec.Replicas) 420 } 421 422 return nil 423 } 424 425 func setDeploymentStrategy(instance *current.IBPConsole, d *dep.Deployment) { 426 switch instance.Spec.UsingRemoteDB() { 427 case false: 428 d.Spec.Strategy = appsv1.DeploymentStrategy{ 429 Type: appsv1.RecreateDeploymentStrategyType, 430 } 431 case true: 432 opts := intstr.FromString("25%") 433 d.Spec.Strategy = appsv1.DeploymentStrategy{ 434 Type: appsv1.RollingUpdateDeploymentStrategyType, 435 RollingUpdate: &appsv1.RollingUpdateDeployment{ 436 MaxUnavailable: &opts, 437 MaxSurge: &opts, 438 }, 439 } 440 } 441 } 442 443 func setSpreadConstraints(instance *current.IBPConsole, d *dep.Deployment) { 444 if instance.Spec.UsingRemoteDB() { 445 d.Spec.Template.Spec.TopologySpreadConstraints = []corev1.TopologySpreadConstraint{ 446 { 447 MaxSkew: 1, 448 TopologyKey: "topology.kubernetes.io/zone", 449 WhenUnsatisfiable: corev1.ScheduleAnyway, 450 LabelSelector: &v1.LabelSelector{ 451 MatchLabels: map[string]string{ 452 "type": "ibpconsole", 453 }, 454 }, 455 }, 456 } 457 } 458 }