github.com/verrazzano/verrazzano@v1.7.1/application-operator/controllers/logging/fluentd.go (about) 1 // Copyright (C) 2020, 2022, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package logging 5 6 import ( 7 "context" 8 "fmt" 9 "os" 10 11 "github.com/crossplane/oam-kubernetes-runtime/pkg/oam" 12 vzapi "github.com/verrazzano/verrazzano/application-operator/apis/oam/v1alpha1" 13 "go.uber.org/zap" 14 corev1 "k8s.io/api/core/v1" 15 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 16 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 17 k8sclient "sigs.k8s.io/controller-runtime/pkg/client" 18 ) 19 20 const ( 21 FluentdStdoutSidecarName = "fluentd-stdout-sidecar" 22 fluentdConfKey = "fluentd.conf" 23 fluentdConfMountPath = "/fluentd/etc/fluentd.conf" 24 configMapName = "fluentd-config" 25 confVolume = "fluentd-config-volume" 26 27 scratchVolMountPath = "/scratch" 28 ) 29 30 // DefaultFluentdImage holds the default FLUENTD image that will be used if it is not specified in the logging logInfo 31 var DefaultFluentdImage string 32 33 func init() { 34 DefaultFluentdImage = os.Getenv("DEFAULT_FLUENTD_IMAGE") 35 } 36 37 // FluentdManager is a general interface to interact with FLUENTD related resources 38 type FluentdManager interface { 39 Apply(logInfo *LogInfo, resource vzapi.QualifiedResourceRelation, fluentdPod *FluentdPod) error 40 Remove(logInfo *LogInfo, resource vzapi.QualifiedResourceRelation, fluentdPod *FluentdPod) bool 41 } 42 43 // Fluentd is an implementation of FluentdManager. 44 type Fluentd struct { 45 k8sclient.Client 46 Log *zap.SugaredLogger 47 Context context.Context 48 ParseRules string 49 StorageVolumeName string 50 StorageVolumeMountPath string 51 WorkloadType string 52 } 53 54 // FluentdPod contains pod information for pods which require FLUENTD integration 55 type FluentdPod struct { 56 Containers []corev1.Container 57 Volumes []corev1.Volume 58 VolumeMounts []corev1.VolumeMount 59 HandlerEnv []corev1.EnvVar 60 LogPath string 61 } 62 63 // Apply applies FLUENTD configuration to create/update FLUENTD container, configmap, volumes and volume mounts. 64 // Returns true if any changes are made; false otherwise. 65 func (f *Fluentd) Apply(logInfo *LogInfo, resource vzapi.QualifiedResourceRelation, fluentdPod *FluentdPod) error { 66 if err := f.ensureFluentdConfigMapExists(resource.Namespace); err != nil { 67 return err 68 } 69 70 f.ensureFluentdVolumes(fluentdPod) 71 f.ensureFluentdVolumeMountExists(fluentdPod) 72 f.ensureFluentdContainer(fluentdPod, logInfo, resource.Namespace) 73 74 return nil 75 } 76 77 // Remove removes FLUENTD container, configmap, volumes and volume mounts. 78 // Returns whether the remove action has been verified so that the caller knows when it is safe to forget the association. 79 func (f *Fluentd) Remove(logInfo *LogInfo, resource vzapi.QualifiedResourceRelation, fluentdPod *FluentdPod) bool { 80 configMapVerified := f.removeFluentdConfigMap(resource.Namespace) 81 volumesVerified := f.removeFluentdVolumes(fluentdPod) 82 mountsVerified := f.removeFluentdVolumeMounts(fluentdPod) 83 containersVerified := f.removeFluentdContainer(fluentdPod) 84 85 return configMapVerified && volumesVerified && mountsVerified && containersVerified 86 } 87 88 // ensureFluentdContainer ensures that the FLUENTD container is in the expected state. If a FLUENTD container already 89 // exists, replace it with a container created with the current logInfo information. If no FLUENTD container already 90 // exists, create one and add it to the FluentdPod. 91 func (f *Fluentd) ensureFluentdContainer(fluentdPod *FluentdPod, logInfo *LogInfo, namespace string) { 92 containers := fluentdPod.Containers 93 fluentdContainerIndex := -1 94 // iterate over existing containers looking for FLUENTD container 95 for i, container := range containers { 96 if container.Name == FluentdStdoutSidecarName { 97 // FLUENTD container found, save the index 98 fluentdContainerIndex = i 99 break 100 } 101 } 102 fluentdContainer := f.createFluentdContainer(fluentdPod, logInfo, namespace) 103 if fluentdContainerIndex != -1 { 104 // the index is still the initial -1 so we didn't find an existing FLUENTD container so we replace it 105 containers[fluentdContainerIndex] = fluentdContainer 106 } else { 107 // no existing FLUENTD container was found so add it to the list 108 containers = append(containers, fluentdContainer) 109 } 110 fluentdPod.Containers = containers 111 } 112 113 // ensureFluentdVolumes ensures that the FLUENTD volumes exist. We expect 2 volumes, a FLUENTD volume and a 114 // FLUENTD config map volume. If these already exist, nothing needs to be done. If they don't already exist, 115 // create them and add to the FluentdPod. 116 func (f *Fluentd) ensureFluentdVolumes(fluentdPod *FluentdPod) { 117 volumes := fluentdPod.Volumes 118 configMapVolumeExists := false 119 fluentdVolumeExists := false 120 for _, volume := range volumes { 121 if volume.Name == f.StorageVolumeName { 122 fluentdVolumeExists = true 123 } else if volume.Name == fmt.Sprintf("%s-volume", configMapName) { 124 configMapVolumeExists = true 125 } 126 } 127 if !configMapVolumeExists { 128 volumes = append(volumes, f.createFluentdConfigMapVolume(configMapName)) 129 } 130 if !fluentdVolumeExists { 131 volumes = append(volumes, f.createFluentdEmptyDirVolume()) 132 } 133 fluentdPod.Volumes = volumes 134 } 135 136 // ensureFluentdVolumeMountExists ensures that the FLUENTD volume mount exists. If one already exists, nothing 137 // needs to be done. If it doesn't already exist create one and add it to the FluentdPod. 138 func (f *Fluentd) ensureFluentdVolumeMountExists(fluentdPod *FluentdPod) { 139 volumeMounts := fluentdPod.VolumeMounts 140 storageVolumeMountExists := false 141 for _, volumeMount := range volumeMounts { 142 if volumeMount.Name == f.StorageVolumeName { 143 storageVolumeMountExists = true 144 } 145 } 146 147 // If no storage volume mount exists create one and add it to the list. 148 if !storageVolumeMountExists { 149 volumeMounts = append(volumeMounts, f.createStorageVolumeMount()) 150 } 151 152 fluentdPod.VolumeMounts = volumeMounts 153 } 154 155 // ensureFluentdConfigMapExists ensures that the FLUENTD configmap exists. If it already exists, there is nothing 156 // to do. If it doesn't exist, create it. 157 func (f *Fluentd) ensureFluentdConfigMapExists(namespace string) error { 158 // check if configmap exists 159 configMapExists, err := resourceExists(f.Context, f, configMapAPIVersion, configMapKind, configMapName+"-"+f.WorkloadType, namespace) 160 if err != nil { 161 return err 162 } 163 164 if configMapExists { 165 return f.Update(f.Context, f.createFluentdConfigMap(namespace), &k8sclient.UpdateOptions{}) 166 } 167 return f.Create(f.Context, f.createFluentdConfigMap(namespace), &k8sclient.CreateOptions{}) 168 } 169 170 // createFluentdConfigMap creates the FLUENTD configmap per given namespace. 171 func (f *Fluentd) createFluentdConfigMap(namespace string) *corev1.ConfigMap { 172 return &corev1.ConfigMap{ 173 ObjectMeta: metav1.ObjectMeta{ 174 Name: configMapName + "-" + f.WorkloadType, 175 Namespace: namespace, 176 }, 177 Data: func() map[string]string { 178 var data = make(map[string]string) 179 data[fluentdConfKey] = f.ParseRules 180 return data 181 }(), 182 } 183 } 184 185 // removeFluentdContainer removes FLUENTD container 186 func (f *Fluentd) removeFluentdContainer(fluentdPod *FluentdPod) bool { 187 containers := fluentdPod.Containers 188 fluentdContainerIndex := -1 189 for i, container := range containers { 190 if container.Name == FluentdStdoutSidecarName { 191 fluentdContainerIndex = i 192 break 193 } 194 } 195 196 if fluentdContainerIndex >= 0 { 197 length := len(containers) 198 containers[fluentdContainerIndex] = containers[length-1] 199 containers = containers[:length-1] 200 } 201 202 fluentdPod.Containers = containers 203 // return true when we confirm that the fluentd container has been removed 204 return fluentdContainerIndex == -1 205 } 206 207 // removeFluentdVolumeMounts removes FLUENTD volume mounts 208 func (f *Fluentd) removeFluentdVolumeMounts(fluentPod *FluentdPod) bool { 209 // For now we can't remove the FLUENTD volume mount because we need to keep the logs in scratch 210 // since we can't set 'logHomeEnabled' to false for the wls domain. 211 return true 212 } 213 214 // removeFluentdVolumes removes FLUENTD volumes. There are currently 2 volumes, a FLUENTD volume and a 215 // FLUENTD configmap volume. 216 // Returns true if we have validated that we have already deleted the volumes; false otherwise. This ensures 217 // that we don't remove knowledge of the workload until we have validated that it has been fully cleaned up 218 // in the system. 219 func (f *Fluentd) removeFluentdVolumes(fluentdPod *FluentdPod) bool { 220 // If the FLUENTD configmap volume exists, delete it. 221 // For now we can't remove the FLUENTD volume because we need to keep the logs in scratch 222 // since we can't set 'logHomeEnabled' to false for the wls domain. 223 volumes := fluentdPod.Volumes 224 configMapVolumeName := fmt.Sprintf("%s-volume", configMapName) 225 configMapVolumeIndex := -1 226 for i, volume := range volumes { 227 if volume.Name == configMapVolumeName { 228 configMapVolumeIndex = i 229 break 230 } 231 } 232 233 if configMapVolumeIndex >= 0 { 234 length := len(volumes) 235 volumes[configMapVolumeIndex] = volumes[length-1] 236 volumes = volumes[:length-1] 237 } 238 239 fluentdPod.Volumes = volumes 240 // return true when we verify that volumes have been removed 241 return configMapVolumeIndex == -1 242 } 243 244 // removeFluentdConfigMap removes the FLUENTD configmap 245 func (f *Fluentd) removeFluentdConfigMap(namespace string) bool { 246 configMapExists, err := resourceExists(f.Context, f, configMapAPIVersion, configMapKind, configMapName+"-"+f.WorkloadType, namespace) 247 248 if configMapExists { 249 _ = f.Delete(f.Context, f.createFluentdConfigMap(namespace), &k8sclient.DeleteOptions{}) 250 } 251 // return true when we confirm that the configmap has been successfully deleted 252 return !(configMapExists) && err == nil 253 } 254 255 // createFluentdContainer creates the FLUENTD stdout sidecar container 256 func (f *Fluentd) createFluentdContainer(fluentdPod *FluentdPod, logInfo *LogInfo, namespace string) corev1.Container { 257 container := corev1.Container{ 258 Name: FluentdStdoutSidecarName, 259 Args: []string{"-c", "/etc/fluent.conf"}, 260 Image: logInfo.FluentdImage, 261 ImagePullPolicy: corev1.PullIfNotPresent, 262 Env: []corev1.EnvVar{ 263 { 264 Name: "LOG_PATH", 265 Value: fluentdPod.LogPath, 266 }, 267 { 268 Name: "FLUENTD_CONF", 269 Value: fluentdConfKey, 270 }, 271 { 272 Name: "NAMESPACE", 273 Value: namespace, 274 }, 275 { 276 Name: "APP_CONF_NAME", 277 ValueFrom: &corev1.EnvVarSource{ 278 FieldRef: &corev1.ObjectFieldSelector{ 279 FieldPath: "metadata.labels['" + oam.LabelAppName + "']", 280 }, 281 }, 282 }, 283 { 284 Name: "COMPONENT_NAME", 285 ValueFrom: &corev1.EnvVarSource{ 286 FieldRef: &corev1.ObjectFieldSelector{ 287 FieldPath: "metadata.labels['" + oam.LabelAppComponent + "']", 288 }, 289 }, 290 }, 291 }, 292 VolumeMounts: []corev1.VolumeMount{ 293 { 294 MountPath: fluentdConfMountPath, 295 Name: confVolume, 296 SubPath: fluentdConfKey, 297 ReadOnly: true, 298 }, 299 { 300 MountPath: f.StorageVolumeMountPath, 301 Name: f.StorageVolumeName, 302 ReadOnly: true, 303 }, 304 }, 305 } 306 307 // add handler specific env vars 308 container.Env = append(container.Env, fluentdPod.HandlerEnv...) 309 310 return container 311 } 312 313 // createFluentdEmptyDirVolume creates an empty FLUENTD directory volume 314 func (f *Fluentd) createFluentdEmptyDirVolume() corev1.Volume { 315 return corev1.Volume{ 316 Name: f.StorageVolumeName, 317 VolumeSource: corev1.VolumeSource{ 318 EmptyDir: &corev1.EmptyDirVolumeSource{}, 319 }, 320 } 321 } 322 323 // createFluentdConfigMapVolume creates a FLUENTD configmap volume 324 func (f *Fluentd) createFluentdConfigMapVolume(name string) corev1.Volume { 325 return corev1.Volume{ 326 Name: fmt.Sprintf("%s-volume", name), 327 VolumeSource: corev1.VolumeSource{ 328 ConfigMap: &corev1.ConfigMapVolumeSource{ 329 LocalObjectReference: corev1.LocalObjectReference{ 330 Name: name + "-" + f.WorkloadType, 331 }, 332 DefaultMode: func(mode int32) *int32 { 333 return &mode 334 }(420), 335 }, 336 }, 337 } 338 } 339 340 // createStorageVolumeMount creates a storage volume mount 341 func (f *Fluentd) createStorageVolumeMount() corev1.VolumeMount { 342 return corev1.VolumeMount{ 343 Name: f.StorageVolumeName, 344 MountPath: f.StorageVolumeMountPath, 345 } 346 } 347 348 // resourceExists determines whether or not a resource of the given kind identified by the given name and namespace exists 349 func resourceExists(ctx context.Context, r k8sclient.Reader, apiVersion, kind, name, namespace string) (bool, error) { 350 resources := unstructured.UnstructuredList{} 351 resources.SetAPIVersion(apiVersion) 352 resources.SetKind(kind) 353 err := r.List(ctx, &resources, k8sclient.InNamespace(namespace), k8sclient.MatchingFields{"metadata.name": name}) 354 return len(resources.Items) != 0, err 355 }