github.com/alibaba/ilogtail/pkg@v0.0.0-20250526110833-c53b480d046c/helper/containercenter/container_center_file_discover.go (about) 1 // Copyright 2021 iLogtail Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package containercenter 16 17 import ( 18 "context" 19 "encoding/json" 20 "fmt" 21 "io/fs" 22 "os" 23 "path/filepath" 24 "sort" 25 "strconv" 26 "strings" 27 "sync" 28 "time" 29 30 "github.com/alibaba/ilogtail/pkg/helper" 31 "github.com/alibaba/ilogtail/pkg/logger" 32 "github.com/alibaba/ilogtail/pkg/util" 33 34 "github.com/docker/docker/api/types" 35 "github.com/docker/docker/api/types/container" 36 ) 37 38 const staticContainerInfoPathEnvKey = "ALIYUN_LOG_STATIC_CONTAINER_INFO" 39 40 // const staticContainerType = "ALIYUN_LOG_STATIC_CONTAINER_TYPE" 41 // const staticContainerDScanInterval = "ALIYUN_LOG_STATIC_CONTAINED_SCAN_INTERVAL" 42 // const staticContainerTypeContainerD = "containerd" 43 44 var containerdScanIntervalMs = 1000 45 var staticDockerContainers []types.ContainerJSON 46 var staticDockerContainerError error 47 var staticDockerContainerLock sync.Mutex 48 var loadStaticContainerOnce sync.Once 49 var staticDockerContainerLastStat helper.StateOS 50 var staticDockerContainerFile string 51 var staticDockerContainerLastBody string 52 53 type staticContainerMount struct { 54 Source string 55 Destination string 56 Driver string 57 } 58 59 type staticContainerState struct { 60 Status string 61 Pid int 62 } 63 64 type staticContainerInfo struct { 65 ID string 66 Name string 67 Created string 68 HostName string 69 IP string 70 Image string 71 LogPath string 72 Labels map[string]string 73 LogType string 74 UpperDir string 75 Env map[string]string 76 Mounts []staticContainerMount 77 State staticContainerState 78 } 79 80 func staticContainerInfoToStandard(staticInfo *staticContainerInfo, stat fs.FileInfo) types.ContainerJSON { 81 created, err := time.Parse(time.RFC3339Nano, staticInfo.Created) 82 if err != nil { 83 created = stat.ModTime() 84 } 85 86 allEnv := make([]string, 0, len(staticInfo.Env)) 87 for key, val := range staticInfo.Env { 88 allEnv = append(allEnv, key+"="+val) 89 } 90 91 if staticInfo.HostName == "" { 92 staticInfo.HostName = util.GetHostName() 93 } 94 95 if staticInfo.IP == "" { 96 staticInfo.IP = util.GetIPAddress() 97 } 98 99 status := staticInfo.State.Status 100 if status != ContainerStatusExited { 101 if !ContainerProcessAlive(staticInfo.State.Pid) { 102 status = ContainerStatusExited 103 } 104 } 105 106 dockerContainer := types.ContainerJSON{ 107 ContainerJSONBase: &types.ContainerJSONBase{ 108 ID: staticInfo.ID, 109 Name: staticInfo.Name, 110 Created: created.Format(time.RFC3339Nano), 111 LogPath: staticInfo.LogPath, 112 HostConfig: &container.HostConfig{ 113 LogConfig: container.LogConfig{ 114 Type: staticInfo.LogType, 115 }, 116 }, 117 GraphDriver: types.GraphDriverData{ 118 Name: "overlay", 119 Data: map[string]string{ 120 "UpperDir": staticInfo.UpperDir, 121 }, 122 }, 123 State: &types.ContainerState{ 124 Status: status, 125 Pid: staticInfo.State.Pid, 126 }, 127 }, 128 Config: &container.Config{ 129 Labels: staticInfo.Labels, 130 Image: staticInfo.Image, 131 Env: allEnv, 132 Hostname: staticInfo.HostName, 133 }, 134 NetworkSettings: &types.NetworkSettings{ 135 DefaultNetworkSettings: types.DefaultNetworkSettings{ 136 IPAddress: staticInfo.IP, 137 }, 138 }, 139 } 140 141 for _, mount := range staticInfo.Mounts { 142 dockerContainer.Mounts = append(dockerContainer.Mounts, types.MountPoint{ 143 Source: filepath.Clean(mount.Source), 144 Destination: filepath.Clean(mount.Destination), 145 Driver: mount.Driver, 146 }) 147 } 148 sortMounts := func(mounts []types.MountPoint) { 149 sort.Slice(mounts, func(i, j int) bool { 150 return mounts[i].Source < mounts[j].Source 151 }) 152 } 153 sortMounts(dockerContainer.Mounts) 154 return dockerContainer 155 } 156 157 func overWriteSymLink(oldname, newname string) error { 158 if _, err := os.Lstat(newname); err == nil { 159 if err = os.Remove(newname); err != nil { 160 return fmt.Errorf("failed to unlink: %+v", err) 161 } 162 } else if !os.IsNotExist(err) { 163 return fmt.Errorf("failed to check symlink: %+v", err) 164 } 165 return os.Symlink(oldname, newname) 166 } 167 168 func scanContainerdFilesAndReLink(filePath string) { 169 const logSuffix = ".log" 170 dirPath := filepath.Dir(filePath) 171 172 maxFileNo := 0 173 if err := overWriteSymLink(filepath.Join(dirPath, strconv.Itoa(maxFileNo)+logSuffix), filePath); err != nil { 174 logger.Error(context.Background(), "overwrite symbol link error, from", maxFileNo, "to", filePath, "error", err) 175 } else { 176 logger.Info(context.Background(), "overwrite symbol link success, from", maxFileNo, "to", filePath) 177 } 178 for { 179 time.Sleep(time.Millisecond * time.Duration(containerdScanIntervalMs)) 180 dir, err := os.ReadDir(dirPath) 181 if err != nil { 182 continue 183 } 184 185 for _, fi := range dir { 186 if fi.IsDir() { 187 continue 188 } 189 190 fileName := fi.Name() 191 if ok := strings.HasSuffix(fi.Name(), logSuffix); !ok { 192 continue 193 } 194 baseName := fileName[0 : len(fileName)-len(logSuffix)] 195 fileNo, err := strconv.Atoi(baseName) 196 // not x.log 197 if err != nil { 198 continue 199 } 200 if fileNo <= maxFileNo { 201 continue 202 } 203 if err := overWriteSymLink(filepath.Join(dirPath, strconv.Itoa(fileNo)+logSuffix), filePath); err == nil { 204 logger.Info(context.Background(), "overwrite symbol link success, from", fileNo, "to", filePath, "max", maxFileNo) 205 maxFileNo = fileNo 206 } else { 207 logger.Error(context.Background(), "overwrite symbol link error, from", fileNo, "to", filePath, "error", err, "max", maxFileNo) 208 } 209 } 210 } 211 } 212 213 func innerReadStatisContainerInfo(file string, lastContainerInfo []types.ContainerJSON, stat fs.FileInfo) (containers []types.ContainerJSON, removed []string, changed bool, err error) { 214 body, err := os.ReadFile(filepath.Clean(file)) 215 if err != nil { 216 return nil, nil, false, err 217 } 218 var staticContainerInfos []staticContainerInfo 219 err = json.Unmarshal(body, &staticContainerInfos) 220 if err != nil { 221 return nil, nil, false, err 222 } 223 224 if staticDockerContainerLastBody == "" { 225 changed = true 226 } else if staticDockerContainerLastBody != string(body) { 227 changed = true 228 } 229 staticDockerContainerLastBody = string(body) 230 231 nowIDs := make(map[string]struct{}) 232 for i := 0; i < len(staticContainerInfos); i++ { 233 containers = append(containers, staticContainerInfoToStandard(&staticContainerInfos[i], stat)) 234 nowIDs[staticContainerInfos[i].ID] = struct{}{} 235 } 236 237 for _, lastContainer := range lastContainerInfo { 238 if _, ok := nowIDs[lastContainer.ID]; !ok { 239 removed = append(removed, lastContainer.ID) 240 } 241 } 242 logger.Infof(context.Background(), "read static container info: %#v", staticContainerInfos) 243 return containers, removed, changed, err 244 } 245 246 func isStaticContainerInfoEnabled() bool { 247 envPath := os.Getenv(staticContainerInfoPathEnvKey) 248 return len(envPath) != 0 249 } 250 251 func tryReadStaticContainerInfo() ([]types.ContainerJSON, []string, bool, error) { 252 statusChanged := false 253 loadStaticContainerOnce.Do( 254 func() { 255 envPath := os.Getenv(staticContainerInfoPathEnvKey) 256 if len(envPath) == 0 { 257 return 258 } 259 staticDockerContainerFile = envPath 260 stat, err := os.Stat(staticDockerContainerFile) 261 staticDockerContainers, _, statusChanged, staticDockerContainerError = innerReadStatisContainerInfo(staticDockerContainerFile, nil, stat) 262 if err == nil { 263 staticDockerContainerLastStat = helper.GetOSState(stat) 264 } 265 // not used yet 266 // if containerType := os.Getenv(staticContainerType); containerType == staticContainerTypeContainerD { 267 // if containerDScanInterval := os.Getenv(staticContainerDScanInterval); containerDScanInterval != "" { 268 // interval, err := strconv.Atoi(containerDScanInterval) 269 // if err != nil && interval > 0 && interval < 24*3600 { 270 // containerdScanIntervalMs = interval 271 // } 272 // } 273 // for i := 0; i < len(staticContainerInfos); i++ { 274 // go scanContainerdFilesAndReLink(GetMountedFilePath(staticContainerInfos[i].LogPath)) 275 // } 276 // } 277 }, 278 ) 279 280 if staticDockerContainerFile == "" { 281 return nil, nil, false, nil 282 } 283 284 for _, container := range staticDockerContainers { 285 if container.State.Status == ContainerStatusRunning && !ContainerProcessAlive(container.State.Pid) { 286 container.State.Status = ContainerStatusExited 287 statusChanged = true 288 } 289 } 290 291 stat, err := os.Stat(staticDockerContainerFile) 292 if err != nil { 293 return staticDockerContainers, nil, statusChanged, err 294 } 295 296 osStat := helper.GetOSState(stat) 297 if !osStat.IsChange(staticDockerContainerLastStat) && staticDockerContainerError == nil { 298 return staticDockerContainers, nil, statusChanged, nil 299 } 300 301 newStaticDockerContainer, removedIDs, changed, staticDockerContainerError := innerReadStatisContainerInfo(staticDockerContainerFile, staticDockerContainers, stat) 302 changed = changed || statusChanged 303 if staticDockerContainerError == nil { 304 staticDockerContainers = newStaticDockerContainer 305 } 306 staticDockerContainerLastStat = osStat 307 308 logger.Info(context.Background(), "static docker container info file changed or last read error, last", staticDockerContainerLastStat, 309 "now", osStat, "removed ids", removedIDs, "error", staticDockerContainerError) 310 return staticDockerContainers, removedIDs, changed, staticDockerContainerError 311 }