This is the 14th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

Find the use caseexamples/buildkit0/buildkit.goAfter that, Yuan Xiaobai spirit was invigorated, tried to run a test, can run through:It doesn’t seem like there’s a lot of information in the results, but it shows that you can use some log information to help you understand and become more confident.

main

func main(a) {
   var opt buildOpt
   flag.BoolVar(&opt.withContainerd, "with-containerd".true."enable containerd worker")
   flag.StringVar(&opt.containerd, "containerd"."v1.2.9"."containerd version")
   flag.StringVar(&opt.runc, "runc"."V1.0.0 - rc8"."runc version")
   flag.Parse()

   bk := buildkit(opt)
   out := bk.Run(llb.Shlex("ls -l /bin")) // debug output

   dt, err := out.Marshal(context.TODO(), llb.LinuxAmd64)
   iferr ! =nil {
      panic(err)
   }
   llb.WriteTo(dt, os.Stdout)
}
Copy the code
  • First set parameters, including Boolean and character types, to indicate whether to usecontainerdAs well ascontainerdandruncVersion information of. As far as Yuan knows, Containerd is the container runtime management tool provided by CNCF, while RUNc is the container runtime tool.
  • Initialize buildKit with the configuration option opt.
  • Then run the script commands with the initialized BuildKitls -l /binTo list all files in the /bin directory
  • Marshal the entire data structure and generate DT (Definition) based on Linux operating system and AMD64 architecture standard
  • Finally, the result is printed to standard output os.stdout

goBuildBase

The buildKit method, which relies on goBuildBase, is used to create SRC, which is the source image

func goBuildBase(a) llb.State {
   goAlpine := llb.Image("Docker. IO/library/golang: 1.17 - alpine")
   return goAlpine.
      AddEnv("PATH"."/usr/local/go/bin:"+system.DefaultPathEnvUnix).
      AddEnv("GOPATH"."/go").
      Run(llb.Shlex("apk add --no-cache g++ linux-headers")).
      Run(llb.Shlex("apk add --no-cache git libseccomp-dev make")).Root()
}
Copy the code
  • Llb. Image creates goAlpine, creating a mirrored instance and specifying the ref index
  • Add environment variables and run the dependencies associated with the command line installation on top of the goAlpine image

Think of it as creating a golang base container with the environment set up

copy

The relatively complicated operation in BuildKit is copy:

func copy(src llb.State, srcPath string, dest llb.State, destPath string) llb.State {
   cpImage := llb.Image("docker.io/library/alpine:latest")
   cp := cpImage.Run(llb.Shlexf("cp -a /src%s /dest%s", srcPath, destPath))
   cp.AddMount("/src", src)
   return cp.AddMount("/dest", dest)
}
Copy the code
  • Create a base image just for copying without installing any dependencies at all
  • Run the operation to copy all files from the source directory to the destination directory
  • Mount the source directory
  • Mount directory directory and returns the mount result

SrcPath, destPath, SRC, dest, llB. State: llB. State: llB. State: llB. State: LLB. We can use llb.State to indicate the operation relationship between different mirrors, which is not possible with current Dockerfiles. Seeing this, Yuan xiaobai suddenly understood further, buildKit positioning – the future of image building tools.

runc

Before we look at BuildKit, let’s take a final look at one of the operations, runc:

func runc(version string) llb.State {
   return goBuildBase().
      Run(llb.Shlex("git clone https://github.com/opencontainers/runc.git /go/src/github.com/opencontainers/runc")).
      Dir("/go/src/github.com/opencontainers/runc").
      Run(llb.Shlexf("git checkout -q %s", version)).
      Run(llb.Shlex("go build -o /usr/bin/runc ./")).Root()
}
Copy the code

Similar to the above, based on the golang base image, download the runc source, go to the source directory, switch to the specified version, and finally build the runc runable binary and export it to the./ current directory, return to root directory llb.state

buildkit

func buildkit(opt buildOpt) llb.State {
   src := goBuildBase().
      Run(llb.Shlex("git clone https://github.com/moby/buildkit.git /go/src/github.com/moby/buildkit")).
      Dir("/go/src/github.com/moby/buildkit")

   buildkitdOCIWorkerOnly := src.
      Run(llb.Shlex("go build -o /bin/buildkitd.oci_only -tags no_containerd_worker ./cmd/buildkitd"))

   buildkitd := src.
      Run(llb.Shlex("go build -o /bin/buildkitd ./cmd/buildkitd"))

   buildctl := src.
      Run(llb.Shlex("go build -o /bin/buildctl ./cmd/buildctl"))

   r := llb.Image("docker.io/library/alpine:latest")
   r = copy(buildctl.Root(), "/bin/buildctl", r, "/bin/")
   r = copy(runc(opt.runc), "/usr/bin/runc", r, "/bin/")
   if opt.withContainerd {
     ...
   } else {
      r = copy(buildkitdOCIWorkerOnly.Root(), "/bin/buildkitd.oci_only", r, "/bin/")}return r
}
Copy the code
  • Create buildKit source Golang environment image,git cloneBuildkit source, and go to the source directory
  • Build the buildKitd.oci_only binary based on the source
  • Build the Buildkitd binaries based on the source
  • Build the buildCTL binary based on the source
  • Create an empty image
  • Copy the buildctl binary from above to the newly created image /bin/ directory
  • Copy the runc binary from above to the /bin directory
  • Assuming we don’t set containerd, the next step is to copy the buildkitd.oci_only binary to the /bin directory

Buildctl, runc, buildkitd.oci_only will be displayed when we run the ls -L /bin script.

But if you look at the top output, it doesn’t look like the result of the run, it looks more like an intermediate process, which is Definition. Overall, I can build buildKit family buckets with an image. -buildctl, buildkitd, buildkitd.oci_only. An image is then used to prepare the RUNc command-line tool. Copy is performed with a brand new image. Finally, a clean image holds all the tools created.

Yuan Xiaobai’s curiosity was thoroughly aroused:

  • How does the LLB organize these relationships?
  • How does llb.state work?
  • If this is an intermediate State, what is the relationship between Definition and llb.State? Is Definition the final state?
  • There are several steps that can be run at the same time, so how do you efficiently perform these operations and untangle these dependencies?

Yuan Xiaobai think more excited – LLB is quite simple to use, a look to understand, can be realized, as if it is really not simple!

Next: Dig deeper into Moby Buildkit #15 – the magic of llB. State