How to pass `--gpus all` option to Docker with Go SDK? - docker

I have seen how to do some basic commands such as running a container, pulling images, listing images, etc from the SDK examples.
I am working on a project where I need to use the GPU from within the container.
My system has GPU, I have installed the drivers, and I have also installed the nvidia-container-runtime.
If we remove Go SDK from the scene for a moment, I can run the following command to get the nvidia-smi output on my host system:
docker run -it --rm --gpus all nvidia/cuda:10.0-base nvidia-smi
I have to do this via the SDK. Here is the code to start with. This code prints "hello world". But in actual I will be running nvidia-smi command at that place:
package main
import (
"context"
"os"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/stdcopy"
)
func main() {
ctx := context.Background()
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
panic(err)
}
RunContainer(ctx, cli)
}
func RunContainer(ctx context.Context, cli *client.Client) {
reader, err := cli.ImagePull(ctx, "nvidia/cuda:10.0-base", types.ImagePullOptions{})
if err != nil {
panic(err)
}
defer reader.Close()
// io.Copy(os.Stdout, reader)
resp, err := cli.ContainerCreate(ctx, &container.Config{
Image: "nvidia/cuda:10.0-base",
Cmd: []string{"echo", "hello world"},
// Tty: false,
}, nil, nil, nil, "")
if err != nil {
panic(err)
}
if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
panic(err)
}
statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
select {
case err := <-errCh:
if err != nil {
panic(err)
}
case <-statusCh:
}
out, err := cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true})
if err != nil {
panic(err)
}
stdcopy.StdCopy(os.Stdout, os.Stderr, out)
}

see: https://github.com/docker/cli/blob/9ac8584acfd501c3f4da0e845e3a40ed15c85041/cli/command/container/opts.go#L594
import "github.com/docker/cli/opts"
// ...
gpuOpts := opts.GpuOpts{}
gpuOpts.Set("all")
resp, err := cli.ContainerCreate(ctx, &container.Config{
Image: "nvidia/cuda:10.0-base",
Cmd: []string{"echo", "hello world"},
// Tty: false,
}, &container.HostConfig{Resources: container.Resources{DeviceRequests: gpuOpts.Value()}}, nil, nil, "")

Related

Read docker containers logs in JSON format - Golang

I have a requirement to fetch docker container's logs, I'm using below code to fetch docker logs,
ctx := context.Background()
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
panic(err)
}
options := types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Since: "",
Until: "",
Timestamps: false,
Follow: true,
Tail: "",
Details: true,
}
out, err := cli.ContainerLogs(ctx, "bcd693465a62", options)
if err != nil {
panic(err)
}
buf := new(strings.Builder)
_, err = io.Copy(buf, out)
if err != nil {
fmt.Println(err)
}
fmt.Printf("%s", buf)
My problem is I'm getting docker logs in two different format, for some containers it's just plain text
exec /entrypoint.sh: no such file or directory
but in containerID-json.log file it's showing in below format
{"log":"exec /entrypoint.sh: no such file or directory\n","stream":"stderr","time":"2022-09-06T12:23:57.741316145Z"}
and for some containers it's showing in dynamic format/schema like
time="2022-09-05T02:13:44Z" level=debug msg="cleanup aborting ingest" ref="buildkit/1/layer-sha256:2774afd0c4d3ded992c58f3b5e5939d091bd26f40e507c6dc21dcbd8b7ff486f"
time="2022-09-05T02:13:44Z" level=debug msg="content garbage collected" d=2.971574ms
How I can collect all docker container's logs in same schema/format so it can be stored in below JSON format?
{
"containerID": "bcd693465a62",
"logs": [
{"log":"exec /entrypoint.sh: no such file or directory\n","stream":"stderr","time":"2022-09-06T12:23:57.741316145Z"},
{"log":"cleanup aborting ingest","stream":"stdout","time":"2022-09-05T02:13:44Z"}
]
}
Any help is appreciated
I wrote below code with the help of code reference by #BrianWagner to fulfill my requirements (It works as expected), please suggest if it can be done better way, or if any improvements.
package main
import (
"context"
"encoding/binary"
"fmt"
"io"
"log"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
)
type DockerContainerLog struct {
ContainerID string `json:"containerID"`
DockerLogs []DockerLog `json:"dockerLogs"`
}
type DockerLog struct {
Time string `json:"time"`
Stream string `json:"stream"`
Log string `json:"log"`
}
func main() {
logs := getContainerLogs("cd3d8362ba45")
fmt.Print(logs)
}
func getContainerLogs(cid string) DockerContainerLog {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
panic(err)
}
options := types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Since: "",
Until: "",
Timestamps: true,
Follow: true,
Tail: "",
Details: false,
}
reader, err := cli.ContainerLogs(context.Background(), cid, options)
if err != nil {
log.Fatal(err)
}
defer reader.Close()
dockerLogs := DockerContainerLog{ContainerID: cid}
hdr := make([]byte, 8)
for {
var docLog DockerLog
_, err := reader.Read(hdr)
if err != nil {
if err == io.EOF {
return dockerLogs
}
log.Fatal(err)
}
count := binary.BigEndian.Uint32(hdr[4:])
dat := make([]byte, count)
_, err = reader.Read(dat)
if err != nil && err != io.EOF {
log.Fatal(err)
}
time, log, found := strings.Cut(string(dat), " ")
if found {
docLog.Time = time
docLog.Log = log
switch hdr[0] {
case 1:
docLog.Stream = "Stdout"
default:
docLog.Stream = "Stderr"
}
dockerLogs.DockerLogs = append(dockerLogs.DockerLogs, docLog)
}
}
}

Copy a directory recursively from a container to host using docker client go library

I want to recursively copy a directory from a container on the host.
I'm new to golang and fully aware about docker cp as an alternative but i need to do it via client go library of docker
I was able to copy a file from a container to host, but unable to do so for directories.
Here's what i have till now
package main
import (
"archive/tar"
"context"
"fmt"
"io"
"os"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
)
func main() {
sourcePath := "/test/file.txt"
destinationPath := "/home/user/"
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
panic(err)
}
myContainer, err := cli.ContainerCreate(context.Background(), &container.Config{
Image: "alpine:test",
}, nil, nil, nil,"")
if err != nil {
fmt.Println(err.Error(), "error occurred")
}
tarStream, _, err := cli.CopyFromContainer(context.Background(), myContainer.ID, sourcePath)
if err != nil {
fmt.Println("something went wrong", err)
}
tr := tar.NewReader(tarStream)
if _, err := tr.Next(); err != nil {
panic(err)
}
dst, err := os.Create(destinationPath + "file.txt")
if err != nil {
fmt.Println("error occurred", err)
}
defer dst.Close()
_, err = io.Copy(dst, tr)
if err != nil {
fmt.Println("error", err)
}
cli.ContainerRemove(context.Background(), myContainer.ID, types.ContainerRemoveOptions{})
}
But actually i want to copy the whole directory inside the container /test/ recursively on the host.

Programmatically check if Docker container process ended with non-zero status

I'm working on a Go application which starts some Docker containers using Go Docker SDK. I need to check if containers' processes exit with zero (success) status code.
Here's the minimal working example:
package main
import (
"context"
"io"
"log"
"os"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
)
func main() {
ctx := context.Background()
cli, err := client.NewEnvClient()
if err != nil {
log.Fatal(err)
}
reader, err := cli.ImagePull(
ctx,
"docker.io/library/alpine",
types.ImagePullOptions{},
)
if err != nil {
log.Fatal(err)
}
io.Copy(os.Stdout, reader)
resp, err := cli.ContainerCreate(ctx, &container.Config{
Image: "alpine",
Cmd: []string{"sh", "-c", "echo hello world; return 1"},
Tty: true,
}, nil, nil, "")
if err != nil {
log.Fatal(err)
}
err = cli.ContainerStart(
ctx,
resp.ID,
types.ContainerStartOptions{},
)
if err != nil {
log.Fatal(err)
}
statusCh, errCh := cli.ContainerWait(
ctx,
resp.ID,
container.WaitConditionNotRunning,
)
select {
case err := <-errCh:
if err != nil {
log.Fatal(err)
}
case <-statusCh:
}
out, err := cli.ContainerLogs(
ctx,
resp.ID,
types.ContainerLogsOptions{ShowStdout: true},
)
if err != nil {
log.Fatal(err)
}
io.Copy(os.Stdout, out)
}
As you can see, the process in the container ends with non-zero status (sh -c "echo hello world; return 1"). However, it doesn't log any fatal errors and simply displays hello world when built and executed:
{"status":"Pulling from library/alpine","id":"latest"}
{"status":"Digest: sha256:7043076348bf5040220df6ad703798fd8593a0918d06d3ce30c6c93be117e430"}
{"status":"Status: Image is up to date for alpine:latest"}
hello world
How can I check that container process exited with non-zero status using Docker Go SDK?
I think you should use the status channel to get the exit code. The error channel seems to be used to signal if there was an error while talking to the docker daemon, see https://godoc.org/github.com/docker/docker/client#Client.ContainerWait.
This works for me:
select {
case err := <-errCh:
if err != nil {
log.Fatal(err)
}
case status := <-statusCh:
log.Printf("status.StatusCode: %#+v\n", status.StatusCode)
}

How can I get Docker container output through a websocket?

I am trying to send output from a docker container to the console using fmt, but when trying to do it i get this.
&{0xc0422a65c0 {0 0} false <nil> 0x6415a0 0x641540}
How do I do this? This is my full code.
func main() {
imageName := "hidden/hidden"
ctx := context.Background()
cli, err := client.NewClient("tcp://0.0.0.0:0000", "v0.00", nil, nil)
if err != nil {
panic(err)
}
fmt.Println("Pulling \"" + imageName + "\"")
_, err = cli.ImagePull(ctx, imageName, types.ImagePullOptions{})
if err != nil {
panic(err)
}
containerConfig := &container.Config{
Image: imageName,
Cmd: []string{"./app/start.sh", "--no-wizard"},
}
resp, err := cli.ContainerCreate(ctx, containerConfig, nil, nil, "")
if err != nil {
panic(err)
}
if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
panic(err)
}
timer := time.NewTimer(time.Minute)
go func() {
<-timer.C
if err := cli.ContainerStop(ctx, resp.ID, nil); err != nil {
panic(err)
}
}()
out, err := cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true})
if err != nil {
panic(err)
}
io.Copy(os.Stdout, out) // This is what I want to change to print with "fmt".
}
Tried: (but does not display until container is done.)
buf := new(bytes.Buffer)
buf.ReadFrom(out)
fmt.Println(buf.String())
Intention: Allow real-time console output to the web.
This seems to be the answer to my question, I did some searching about scanners as Cerise Limón commented. Anyone else who seems to be having the issue that I did can use this code. Thanks to all that helped.
scanner := bufio.NewScanner(out)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
&{0xc0422a65c0 {0 0} false <nil> 0x6415a0 0x641540} is not a bad output. This is a perfectly fine struct output. I think the the main problem is here just your lack of golang experience.
I'm a beginner as well and I can imagine that when you see an output like above you thought that "I made a mistake"
No you didn't. It's default fmt behavior when u try to print out a struct which contains pointers.
Checkout this instead of your fmt.Println:
fmt.Printf("%+v\n", out)
Well this answer stands on my assumptions but if this is the case just ping me for more info.

Docker Golang API create a container with files from memory

Currently, to get files into a container using the golang api I first must create the container and then use the CopyToContainer function (Example Below).
Is it possible to create a container and specify files for it to have at create time, without having the files first on the file system?
Example 1)
func main() {
cli, err := client.NewEnvClient()
if err != nil {
panic(err)
}
resp, err := cli.ContainerCreate(context.Background(),
&container.Config{
Image: "alpine",
Cmd: []string{"ls", "/"},
}, nil, nil, "testContainer")
if err != nil {
panic(err)
}
fmt.Printf("Created: %v\n", resp.ID)
cli.CopyToContainer(context.Background(), resp.ID, "/", getTar(),types.CopyToContainerOptions{})
}
func getTar() io.Reader {
...
}
EDIT:
- Code spacing.
I found this solution, but I can't get the file to show up in the container. Try it and let me know if it works for you!
var buf bytes.Buffer
tw := tar.NewWriter(&buf)
err = tw.WriteHeader(&tar.Header{
Name: filename, // filename
Mode: 0777, // permissions
Size: int64(len(content)), // filesize
})
if err != nil {
return nil, fmt.Errorf("docker copy: %v", err)
}
tw.Write([]byte(content))
tw.Close()
// use &buf as argument for content in CopyToContainer
cli.CopyToContainer(context.Background(), resp.ID, "/", &buf,types.CopyToContainerOptions{})

Resources