github.com/projecteru2/core@v0.0.0-20240321043226-06bcc1c23f58/engine/docker/helper.go (about) 1 package docker 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/base64" 7 "encoding/json" 8 "fmt" 9 "io" 10 "math" 11 "net" 12 "net/http" 13 "net/url" 14 "os" 15 "path/filepath" 16 "strings" 17 "text/template" 18 19 "github.com/docker/distribution/reference" 20 dockertypes "github.com/docker/docker/api/types" 21 "github.com/docker/docker/api/types/blkiodev" 22 dockercontainer "github.com/docker/docker/api/types/container" 23 dockerapi "github.com/docker/docker/client" 24 "github.com/docker/docker/pkg/archive" 25 "github.com/docker/docker/pkg/stdcopy" 26 "github.com/docker/docker/registry" 27 "github.com/docker/go-units" 28 29 corecluster "github.com/projecteru2/core/cluster" 30 "github.com/projecteru2/core/engine" 31 enginetypes "github.com/projecteru2/core/engine/types" 32 "github.com/projecteru2/core/log" 33 "github.com/projecteru2/core/types" 34 coretypes "github.com/projecteru2/core/types" 35 "github.com/projecteru2/core/utils" 36 ) 37 38 type fuckDockerStream struct { 39 conn net.Conn 40 buf io.Reader 41 } 42 43 func (f fuckDockerStream) Read(p []byte) (n int, err error) { 44 return f.buf.Read(p) 45 } 46 47 func (f fuckDockerStream) Close() error { 48 return f.conn.Close() 49 } 50 51 func mergeStream(stream io.ReadCloser) io.Reader { 52 outr, outw := io.Pipe() 53 54 go func() { 55 defer stream.Close() 56 _, err := stdcopy.StdCopy(outw, outw, stream) 57 _ = outw.CloseWithError(err) 58 }() 59 60 return outr 61 } 62 63 // FuckDockerStream will copy docker stream to stdout and err 64 func FuckDockerStream(stream dockertypes.HijackedResponse) io.ReadCloser { 65 outr := mergeStream(io.NopCloser(stream.Reader)) 66 return fuckDockerStream{stream.Conn, outr} 67 } 68 69 // make mount paths 70 // 使用volumes, 参数格式跟docker一样 71 // volumes: 72 // - "/foo-data:$SOMEENV/foodata:rw" 73 func makeMountPaths(ctx context.Context, opts *enginetypes.VirtualizationCreateOptions, resourceOpts *engine.VirtualizationResource) ([]string, map[string]struct{}) { 74 binds := []string{} 75 volumes := make(map[string]struct{}) 76 77 var expandENV = func(env string) string { 78 envMap := map[string]string{} 79 for _, env := range opts.Env { 80 parts := strings.Split(env, "=") 81 envMap[parts[0]] = parts[1] 82 } 83 return envMap[env] 84 } 85 86 for _, path := range resourceOpts.Volumes { 87 expanded := os.Expand(path, expandENV) 88 parts := strings.Split(expanded, ":") 89 if len(parts) == 2 { 90 binds = append(binds, fmt.Sprintf("%s:%s:rw", parts[0], parts[1])) 91 volumes[parts[1]] = struct{}{} 92 } else if len(parts) >= 3 { 93 binds = append(binds, fmt.Sprintf("%s:%s:%s", parts[0], parts[1], parts[2])) 94 volumes[parts[1]] = struct{}{} 95 if len(parts) == 4 { 96 log.WithFunc("engine.docker.makeMountPaths").Warn(ctx, "docker engine not support volume with size limit") 97 } 98 } 99 } 100 101 return binds, volumes 102 } 103 104 func makeResourceSetting(cpu float64, memory int64, cpuMap map[string]int64, numaNode string, IOPSOptions map[string]string, remap bool) dockercontainer.Resources { 105 resource := dockercontainer.Resources{} 106 107 resource.CPUQuota = 0 108 resource.CPUShares = defaultCPUShare 109 resource.CPUPeriod = corecluster.CPUPeriodBase 110 if cpu > 0 { 111 resource.CPUQuota = int64(cpu * float64(corecluster.CPUPeriodBase)) 112 } else if cpu == -1 { 113 resource.CPUQuota = -1 114 } 115 116 if len(cpuMap) > 0 { 117 cpuIDs := []string{} 118 for cpuID := range cpuMap { 119 cpuIDs = append(cpuIDs, cpuID) 120 } 121 resource.CpusetCpus = strings.Join(cpuIDs, ",") 122 // numaNode will empty or numaNode 123 resource.CpusetMems = numaNode 124 125 if remap { 126 resource.CPUShares = int64(1024) 127 } else { 128 // unrestrained cpu quota for binding 129 resource.CPUQuota = -1 130 // cpu share for fragile pieces 131 if _, divpart := math.Modf(cpu); divpart > 0 { 132 resource.CPUShares = int64(math.Round(float64(1024) * divpart)) 133 } 134 } 135 } 136 resource.Memory = memory 137 resource.MemorySwap = memory 138 resource.MemoryReservation = memory / 2 139 if memory != 0 && memory/2 < int64(units.MiB*4) { 140 resource.MemoryReservation = int64(units.MiB * 4) 141 } 142 143 if len(IOPSOptions) > 0 { 144 var readIOPSDevices, writeIOPSDevices, readBPSDevices, writeBPSDevices []*blkiodev.ThrottleDevice 145 for device, options := range IOPSOptions { 146 parts := strings.Split(options, ":") 147 for len(parts) < 4 { 148 parts = append(parts, "0") 149 } 150 var readIOPS, writeIOPS, readBPS, writeBPS int64 151 readIOPS, _ = utils.ParseRAMInHuman(parts[0]) 152 writeIOPS, _ = utils.ParseRAMInHuman(parts[1]) 153 readBPS, _ = utils.ParseRAMInHuman(parts[2]) 154 writeBPS, _ = utils.ParseRAMInHuman(parts[3]) 155 156 readIOPSDevices = append(readIOPSDevices, &blkiodev.ThrottleDevice{ 157 Path: device, 158 Rate: uint64(readIOPS), 159 }) 160 writeIOPSDevices = append(writeIOPSDevices, &blkiodev.ThrottleDevice{ 161 Path: device, 162 Rate: uint64(writeIOPS), 163 }) 164 readBPSDevices = append(readBPSDevices, &blkiodev.ThrottleDevice{ 165 Path: device, 166 Rate: uint64(readBPS), 167 }) 168 writeBPSDevices = append(writeBPSDevices, &blkiodev.ThrottleDevice{ 169 Path: device, 170 Rate: uint64(writeBPS), 171 }) 172 } 173 resource.BlkioDeviceReadIOps = readIOPSDevices 174 resource.BlkioDeviceWriteIOps = writeIOPSDevices 175 resource.BlkioDeviceReadBps = readBPSDevices 176 resource.BlkioDeviceWriteBps = writeBPSDevices 177 } 178 179 return resource 180 } 181 182 // 只要一个image的前面, tag不要 183 func normalizeImage(image string) string { 184 if strings.Contains(image, ":") { 185 t := strings.Split(image, ":") 186 return t[0] 187 } 188 return image 189 } 190 191 // image begin 192 // MakeAuthConfigFromRemote Calculate encoded AuthConfig from registry and eru-core config 193 // See https://github.com/docker/cli/blob/16cccc30f95c8163f0749eba5a2e80b807041342/cli/command/registry.go#L67 194 func makeEncodedAuthConfigFromRemote(authConfigs map[string]coretypes.AuthConfig, remote string) (string, error) { 195 ref, err := reference.ParseNormalizedNamed(remote) 196 if err != nil { 197 return "", err 198 } 199 200 // Resolve the Repository name from fqn to RepositoryInfo 201 repoInfo, err := registry.ParseRepositoryInfo(ref) 202 if err != nil { 203 return "", err 204 } 205 206 serverAddress := repoInfo.Index.Name 207 if authConfig, exists := authConfigs[serverAddress]; exists { 208 if encodedAuth, err := encodeAuthToBase64(authConfig); err == nil { 209 return encodedAuth, nil 210 } 211 return "", err 212 } 213 return "dummy", nil 214 } 215 216 // EncodeAuthToBase64 serializes the auth configuration as JSON base64 payload 217 // See https://github.com/docker/cli/blob/master/cli/command/registry.go#L41 218 func encodeAuthToBase64(authConfig coretypes.AuthConfig) (string, error) { 219 buf, err := json.Marshal(authConfig) 220 if err != nil { 221 return "", err 222 } 223 return base64.URLEncoding.EncodeToString(buf), nil 224 } 225 226 // Image tag 227 // 格式严格按照 Hub/HubPrefix/appname:tag 来 228 func createImageTag(config types.DockerConfig, appname, tag string) string { 229 prefix := strings.Trim(config.Namespace, "/") 230 if prefix == "" { 231 return fmt.Sprintf("%s/%s:%s", config.Hub, appname, tag) 232 } 233 return fmt.Sprintf("%s/%s/%s:%s", config.Hub, prefix, appname, tag) 234 } 235 236 func makeCommonPart(build *enginetypes.Build) (string, error) { 237 tmpl := template.Must(template.New("common").Parse(commonTmpl)) 238 out := bytes.Buffer{} 239 if err := tmpl.Execute(&out, build); err != nil { 240 return "", err 241 } 242 return out.String(), nil 243 } 244 245 func makeUserPart(opts *enginetypes.BuildContentOptions) (string, error) { 246 tmpl := template.Must(template.New("user").Parse(userTmpl)) 247 out := bytes.Buffer{} 248 if err := tmpl.Execute(&out, opts); err != nil { 249 return "", err 250 } 251 return out.String(), nil 252 } 253 254 func makeMainPart(_ *enginetypes.BuildContentOptions, build *enginetypes.Build, from string, commands, copys []string) (string, error) { 255 var buildTmpl []string 256 common, err := makeCommonPart(build) 257 if err != nil { 258 return "", err 259 } 260 buildTmpl = append(buildTmpl, from, common) 261 if len(copys) > 0 { 262 buildTmpl = append(buildTmpl, copys...) 263 } 264 if len(commands) > 0 { 265 buildTmpl = append(buildTmpl, commands...) 266 } 267 return strings.Join(buildTmpl, "\n"), nil 268 } 269 270 // Dockerfile 271 func createDockerfile(dockerfile, buildDir string) error { 272 f, err := os.Create(filepath.Join(buildDir, "Dockerfile")) 273 if err != nil { 274 return err 275 } 276 defer f.Close() 277 _, err = f.WriteString(dockerfile) 278 return err 279 } 280 281 // CreateTarStream create a tar stream 282 func CreateTarStream(path string) (io.ReadCloser, error) { 283 tarOpts := &archive.TarOptions{ 284 ExcludePatterns: []string{}, 285 IncludeFiles: []string{"."}, 286 Compression: archive.Uncompressed, 287 NoLchown: true, 288 } 289 return archive.TarWithOptions(path, tarOpts) 290 } 291 292 // GetIP Get hostIP 293 func GetIP(ctx context.Context, daemonHost string) string { 294 u, err := url.Parse(daemonHost) 295 if err != nil { 296 log.WithFunc("engine.docker.GetIP").Errorf(ctx, err, "GetIP %s failed", daemonHost) 297 return "" 298 } 299 return u.Hostname() 300 } 301 302 func makeDockerClient(_ context.Context, config coretypes.Config, client *http.Client, endpoint string) (engine.API, error) { 303 cli, err := dockerapi.NewClientWithOpts( 304 dockerapi.WithHost(endpoint), 305 dockerapi.WithVersion(config.Docker.APIVersion), 306 dockerapi.WithHTTPClient(client)) 307 if err != nil { 308 return nil, err 309 } 310 return &Engine{cli, config}, nil 311 } 312 313 func useCNI(labels map[string]string) bool { 314 for k, v := range labels { 315 if k == "cni" && v == "1" { 316 return true 317 } 318 } 319 return false 320 }