Docker Engine SDK와 Docker API 사용 예제

Docker Engine SDK와 Docker API 사용 예제

Docker를 설치한 후 Go 또는 Python SDK를 설치하고 Docker Engine API도 시도해 볼 수 있어요. 이 예제들은 Go, Python SDK와 curl을 사용한 HTTP API로 특정 Docker 작업을 수행하는 방법을 각각 보여줘요.

출처: 문서

본문

Docker를 설치한 후 Go 또는 Python SDK를 설치하고 Docker Engine API도 시도해 볼 수 있어요.

이 예제들은 Go와 Python SDK, 그리고 curl을 사용한 HTTP API로 특정 Docker 작업을 수행하는 방법을 각각 보여줘요.

컨테이너 실행

첫 번째 예제는 Docker API로 컨테이너를 실행하는 방법을 보여줘요. 명령줄에서는 docker run 명령을 사용하겠지만, 여러분의 앱에서도 똑같이 쉽게 할 수 있어요.

이것은 명령 프롬프트에서 docker run alpine echo hello world를 입력하는 것과 동일해요.

package main

import (
	"context"
	"io"
	"log"
	"os"

	"github.com/moby/moby/api/pkg/stdcopy"
	"github.com/moby/moby/api/types/container"
	"github.com/moby/moby/client"
)

func main() {
	ctx := context.Background()
	apiClient, err := client.New(client.FromEnv, client.WithUserAgent("my-application/1.0.0"))
	if err != nil {
		log.Fatal(err)
	}
	defer apiClient.Close()

	reader, err := apiClient.ImagePull(ctx, "docker.io/library/alpine", client.ImagePullOptions{})
	if err != nil {
		log.Fatal(err)
	}

	defer reader.Close()
	// cli.ImagePull is asynchronous.
	// The reader needs to be read completely for the pull operation to complete.
	// If stdout is not required, consider using io.Discard instead of os.Stdout.
	io.Copy(os.Stdout, reader)

	resp, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
		Config: &container.Config{
			Cmd: []string{"echo", "hello world"},
			Tty: false,
		},
		Image: "alpine",
	})
	if err != nil {
		log.Fatal(err)
	}

	if _, err := apiClient.ContainerStart(ctx, resp.ID, client.ContainerStartOptions{}); err != nil {
		log.Fatal(err)
	}

	wait := apiClient.ContainerWait(ctx, resp.ID, client.ContainerWaitOptions{})
	select {
	case err := wait.Error:
		if err != nil {
			log.Fatal(err)
		}
	case wait.Result:
	}

	out, err := apiClient.ContainerLogs(ctx, resp.ID, client.ContainerLogsOptions{ShowStdout: true})
	if err != nil {
		log.Fatal(err)
	}

	stdcopy.StdCopy(os.Stdout, os.Stderr, out)
}
import docker
client = docker.from_env()
print(client.containers.run("alpine", ["echo", "hello", "world"]))
$ curl --unix-socket /var/run/docker.sock -H "Content-Type: application/json" \
  -d '{"Image": "alpine", "Cmd": ["echo", "hello world"]}' \
  -X POST http://localhost/v1.56/containers/create
{"Id":"1c6594faf5","Warnings":null}

$ curl --unix-socket /var/run/docker.sock -X POST http://localhost/v1.56/containers/1c6594faf5/start

$ curl --unix-socket /var/run/docker.sock -X POST http://localhost/v1.56/containers/1c6594faf5/wait
{"StatusCode":0}

$ curl --unix-socket /var/run/docker.sock "http://localhost/v1.56/containers/1c6594faf5/logs?stdout=1"
hello world

cURL로 Unix 소켓을 통해 연결할 때 호스트 이름은 중요하지 않아요. 앞선 예제들은 localhost를 사용하지만 어떤 호스트 이름이든 동작해요.

중요 앞선 예제들은 cURL 7.50.0 이상을 사용한다고 가정해요. 이전 버전의 cURL은 소켓 연결 시 비표준 URL 표기법을 사용했어요. 이전 버전의 cURL을 사용한다면 http://를 대신 사용하세요. 예: http:/v1.56/containers/1c6594faf5/start (원문에는 http:// 표기가 누락되어 있습니다)

백그라운드에서 컨테이너 실행

또한 docker run -d bfirsh/reticulate-splines을 입력하는 것과 동등한 백그라운드 컨테이너 실행도 할 수 있어요.

package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"os"

	"github.com/moby/moby/client"
)

func main() {
	ctx := context.Background()
	apiClient, err := client.New(client.FromEnv, client.WithUserAgent("my-application/1.0.0"))
	if err != nil {
		log.Fatal(err)
	}
	defer apiClient.Close()

	imageName := "bfirsh/reticulate-splines"

	out, err := apiClient.ImagePull(ctx, imageName, client.ImagePullOptions{})
	if err != nil {
		log.Fatal(err)
	}
	defer out.Close()

	io.Copy(os.Stdout, out)

	resp, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
		Image: imageName,
	})
	if err != nil {
		log.Fatal(err)
	}

	if _, err := apiClient.ContainerStart(ctx, resp.ID, client.ContainerStartOptions{}); err != nil {
		log.Fatal(err)
	}

	fmt.Println(resp.ID)
}
import docker
client = docker.from_env()
container = client.containers.run("bfirsh/reticulate-splines", detach=True)
print(container.id)
$ curl --unix-socket /var/run/docker.sock -H "Content-Type: application/json" \
  -d '{"Image": "bfirsh/reticulate-splines"}' \
  -X POST http://localhost/v1.56/containers/create
{"Id":"1c6594faf5","Warnings":null}

$ curl --unix-socket /var/run/docker.sock -X POST http://localhost/v1.56/containers/1c6594faf5/start

컨테이너 나열 및 관리

API로 docker ps처럼 실행 중인 컨테이너를 나열할 수 있어요.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/moby/moby/client"
)

func main() {
	ctx := context.Background()
	apiClient, err := client.New(client.FromEnv, client.WithUserAgent("my-application/1.0.0"))
	if err != nil {
		log.Fatal(err)
	}
	defer apiClient.Close()

	containers, err := apiClient.ContainerList(ctx, client.ContainerListOptions{})
	if err != nil {
		log.Fatal(err)
	}

	for _, container := range containers.Items {
		fmt.Println(container.ID)
	}
}
import docker
client = docker.from_env()
for container in client.containers.list():
  print(container.id)
$ curl --unix-socket /var/run/docker.sock http://localhost/v1.56/containers/json
[{
  "Id":"ae63e8b89a26f01f6b4b2c9a7817c31a1b6196acf560f66586fbc8809ffcd772",
  "Names":["/tender_wing"],
  "Image":"bfirsh/reticulate-splines",
  ...
}]

모든 실행 중인 컨테이너 중지

이제 어떤 컨테이너가 있는지 알았으니, 그 컨테이너들에 작업을 수행할 수 있어요. 이 예제는 실행 중인 모든 컨테이너를 중지해요.

Note 프로덕션 서버에서 이 명령을 실행하지 마세요. 또한 swarm 서비스를 사용 중이라면 컨테이너는 중지되지만, Docker가 서비스를 구성된 상태로 유지하기 위해 새 컨테이너를 만들어요.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/moby/moby/client"
)

func main() {
	ctx := context.Background()
	apiClient, err := client.New(client.FromEnv, client.WithUserAgent("my-application/1.0.0"))
	if err != nil {
		log.Fatal(err)
	}
	defer apiClient.Close()

	containers, err := apiClient.ContainerList(ctx, client.ContainerListOptions{})
	if err != nil {
		log.Fatal(err)
	}

	for _, container := range containers.Items {
		fmt.Print("Stopping container ", container.ID[:10], "... ")
		noWaitTimeout := 0 // to not wait for the container to exit gracefully
		if _, err := apiClient.ContainerStop(ctx, container.ID, client.ContainerStopOptions{Timeout: &noWaitTimeout}); err != nil {
			log.Fatal(err)
		}
		fmt.Println("Success")
	}
}
import docker
client = docker.from_env()
for container in client.containers.list():
  container.stop()
$ curl --unix-socket /var/run/docker.sock http://localhost/v1.56/containers/json
[{
  "Id":"ae63e8b89a26f01f6b4b2c9a7817c31a1b6196acf560f66586fbc8809ffcd772",
  "Names":["/tender_wing"],
  "Image":"bfirsh/reticulate-splines",
  ...
}]

$ curl --unix-socket /var/run/docker.sock \
  -X POST http://localhost/v1.56/containers/ae63e8b89a26/stop

특정 컨테이너의 로그 출력

개별 컨테이너에 대해서도 작업을 수행할 수 있어요. 이 예제는 ID가 주어진 컨테이너의 로그를 출력해요. 실행하기 전에 로그를 출력할 컨테이너의 하드코딩된 ID를 변경해야 해요.

package main

import (
	"context"
	"io"
	"log"
	"os"

	"github.com/moby/moby/client"
)

func main() {
	ctx := context.Background()
	apiClient, err := client.New(client.FromEnv, client.WithUserAgent("my-application/1.0.0"))
	if err != nil {
		log.Fatal(err)
	}
	defer apiClient.Close()

	options := client.ContainerLogsOptions{ShowStdout: true}
	// Replace this ID with a container that really exists
	out, err := apiClient.ContainerLogs(ctx, "f1064a8a4c82", options)
	if err != nil {
		log.Fatal(err)
	}

	io.Copy(os.Stdout, out)
}
import docker
client = docker.from_env()
container = client.containers.get('f1064a8a4c82')
print(container.logs())
$ curl --unix-socket /var/run/docker.sock "http://localhost/v1.56/containers/ca5f55cdb/logs?stdout=1"
Reticulating spline 1...
Reticulating spline 2...
Reticulating spline 3...
Reticulating spline 4...
Reticulating spline 5...

모든 이미지 나열

docker image ls와 비슷하게 Engine의 이미지를 나열해요.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/moby/moby/client"
)

func main() {
	ctx := context.Background()
	apiClient, err := client.New(client.FromEnv, client.WithUserAgent("my-application/1.0.0"))
	if err != nil {
		log.Fatal(err)
	}
	defer apiClient.Close()

	images, err := apiClient.ImageList(ctx, client.ImageListOptions{})
	if err != nil {
		log.Fatal(err)
	}

	for _, image := range images.Items {
		fmt.Println(image.ID)
	}
}
import docker
client = docker.from_env()
for image in client.images.list():
  print(image.id)
$ curl --unix-socket /var/run/docker.sock http://localhost/v1.56/images/json
[{
  "Id":"sha256:31d9a31e1dd803470c5a151b8919ef1988ac3efd44281ac59d43ad623f275dcd",
  "ParentId":"sha256:ee4603260daafe1a8c2f3b78fd760922918ab2441cbb2853ed5c439e59c52f96",
  ...
}]

이미지 풀

docker pull처럼 이미지를 풀해요.

package main

import (
	"context"
	"io"
	"log"
	"os"

	"github.com/moby/moby/client"
)

func main() {
	ctx := context.Background()
	apiClient, err := client.New(client.FromEnv, client.WithUserAgent("my-application/1.0.0"))
	if err != nil {
		log.Fatal(err)
	}
	defer apiClient.Close()

	out, err := apiClient.ImagePull(ctx, "alpine", client.ImagePullOptions{})
	if err != nil {
		log.Fatal(err)
	}
	defer out.Close()

	io.Copy(os.Stdout, out)
}
import docker
client = docker.from_env()
image = client.images.pull("alpine")
print(image.id)
$ curl --unix-socket /var/run/docker.sock \
  -X POST "http://localhost/v1.56/images/create?fromImage=alpine"
{"status":"Pulling from library/alpine","id":"3.1"}
{"status":"Pulling fs layer","progressDetail":{},"id":"8f13703509f7"}
{"status":"Downloading","progressDetail":{"current":32768,"total":2244027},"progress":"[\u003e                                                  ] 32.77 kB/2.244 MB","id":"8f13703509f7"}
...

인증을 사용해 이미지 풀

인증을 사용해 docker pull처럼 이미지를 풀해요.

Note 자격 증명은 평문으로 전송돼요. Docker의 공식 레지스트리는 HTTPS를 사용해요. 비공개 레지스트리도 HTTPS를 사용하도록 구성해야 해요.

package main

import (
	"context"
	"io"
	"log"
	"os"

	"github.com/moby/moby/api/pkg/authconfig"
	"github.com/moby/moby/api/types/registry"
	"github.com/moby/moby/client"
)

func main() {
	ctx := context.Background()
	apiClient, err := client.New(client.FromEnv, client.WithUserAgent("my-application/1.0.0"))
	if err != nil {
		log.Fatal(err)
	}
	defer apiClient.Close()

	authStr, err := authconfig.Encode(registry.AuthConfig{
		Username: "username",
		Password: "password",
	})
	if err != nil {
		log.Fatal(err)
	}

	out, err := apiClient.ImagePull(ctx, "alpine", client.ImagePullOptions{RegistryAuth: authStr})
	if err != nil {
		log.Fatal(err)
	}
	defer out.Close()

	io.Copy(os.Stdout, out)
}

Python SDK는 자격 증명 저장소 파일에서 인증 정보를 가져오고 자격 증명 헬퍼와 통합해요. 이 자격 증명을 재정의할 수 있지만, 이 예제 가이드의 범위 밖이에요. docker login을 사용한 후에는 Python SDK가 이 자격 증명을 자동으로 사용해요.

import docker
client = docker.from_env()
image = client.images.pull("alpine")
print(image.id)

이 예제는 자격 증명을 셸의 히스토리에 남기므로, 순진한 구현이라고 생각하세요. 자격 증명은 Base-64로 인코딩된 JSON 구조로 전달돼요.

$ JSON=$(echo '{"username": "string", "password": "string", "serveraddress": "string"}' | base64)

$ curl --unix-socket /var/run/docker.sock \
  -H "Content-Type: application/tar"
  -X POST "http://localhost/v1.56/images/create?fromImage=alpine"
  -H "X-Registry-Auth"
  -d "$JSON"
{"status":"Pulling from library/alpine","id":"3.1"}
{"status":"Pulling fs layer","progressDetail":{},"id":"8f13703509f7"}
{"status":"Downloading","progressDetail":{"current":32768,"total":2244027},"progress":"[\u003e                                                  ] 32.77 kB/2.244 MB","id":"8f13703509f7"}
...

컨테이너 커밋

컨테이너를 커밋해 그 내용에서 이미지를 만들어요.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/moby/moby/api/types/container"
	"github.com/moby/moby/client"
)

func main() {
	ctx := context.Background()
	apiClient, err := client.New(client.FromEnv, client.WithUserAgent("my-application/1.0.0"))
	if err != nil {
		log.Fatal(err)
	}
	defer apiClient.Close()

	createResp, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{
		Config: &container.Config{
			Cmd: []string{"touch", "/helloworld"},
		},
		Image: "alpine",
	})
	if err != nil {
		log.Fatal(err)
	}

	if _, err := apiClient.ContainerStart(ctx, createResp.ID, client.ContainerStartOptions{}); err != nil {
		log.Fatal(err)
	}

	wait := apiClient.ContainerWait(ctx, createResp.ID, client.ContainerWaitOptions{})
	select {
	case err := wait.Error:
		if err != nil {
			log.Fatal(err)
		}
	case wait.Result:
	}

	commitResp, err := apiClient.ContainerCommit(ctx, createResp.ID, client.ContainerCommitOptions{Reference: "helloworld"})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(commitResp.ID)
}
import docker
client = docker.from_env()
container = client.containers.run("alpine", ["touch", "/helloworld"], detach=True)
container.wait()
image = container.commit("helloworld")
print(image.id)
$ docker run -d alpine touch /helloworld
0888269a9d584f0fa8fc96b3c0d8d57969ceea3a64acf47cd34eebb4744dbc52
$ curl --unix-socket /var/run/docker.sock\
  -X POST "http://localhost/v1.56/commit?container=0888269a9d&repo=helloworld"
{"Id":"sha256:6c86a5cd4b87f2771648ce619e319f3e508394b5bfc2cdbd2d60f59d52acda6c"}

더 알아보기 (Learn more)