Go を使用してタスク管理アプリケーションを構築およびデプロイする方法

ゴラン は、開発者がスケーラブルで安全な Web アプリケーションを迅速に開発できるように設計されています。 Goは、使いやすく、安全で、 パフォーマンスの高い Web サーバー 独自のWebテンプレートライブラリと一緒に。 エンタープライズユーザーは、この言語を活用して、迅速なクロスプラットフォーム展開も行います。 その ゴルーチン、ネイティブコンパイル、およびURIベースのパッケージ名前空間により、Goコードは依存関係のない単一の小さなバイナリにコンパイルされるため、非常に高速になります。

開発者はまた、Goのパフォーマンスを支持しています。 同時実行モデル およびCPUのスケーラビリティ。 開発者が内部リクエストを処理する必要があるときはいつでも、Pythonスレッドが行うリソースの10分の1しか消費しない個別のゴルーチンを使用します。 静的リンクを介して、Goは実際にすべての依存関係ライブラリとモジュールをOSとアーキテクチャに基づいて単一のバイナリファイルに結合します。

Go アプリケーションのコンテナー化が重要なのはなぜですか?

Go バイナリは、小さくて自己完結型の実行可能ファイルです。 ただし、アプリケーション コードは、追加のプログラムや Web アプリケーションに適応するため、時間の経過と共に必然的に大きくなります。 これらのアプリには、テンプレート、アセット、データベース構成ファイルが付属している場合があります。 同期が崩れ、依存関係地獄に遭遇し、障害のあるデプロイをプッシュするリスクが高くなります。

コンテナーを使用すると、これらのファイルをバイナリと同期できます。 彼らが また、アプリケーション全体に対して単一のデプロイ可能なユニットを作成するのにも役立ちます。 これには、コード (またはバイナリ)、ランタイム、およびそのシステム ツールまたはライブラリが含まれます。 最終的に 彼らが ローカルでコーディングとテストを行いながら、開発と運用の一貫性を確保できます。

Go アプリケーションのセットアップについて説明し、コンテナー化中の Docker SDK の役割について説明します。

目次

アプリケーションのビルド

このチュートリアルでは、Go を使用して基本的なタスク システム (Gopher) を構築する方法を学習します。

まず、Docker を使用してタスクを実行するシステムを Go で作成します。 次に、アプリケーションの Docker イメージをビルドします。 この例では、 Docker SDK クールなプロジェクトを構築するのに役立ちます。 始めましょう。

主要コンポーネント

始める

始める前に、システムに Goをインストールする 必要があります。 完了したら次の手順に従って、Docker SDK を使用して基本的なタスク管理システムを構築します。

最後にディレクトリ構造を示します。

➜ tree gopher     
gopher
├── go.mod
├── go.sum
├── internal
│   ├── container-manager
│   │   └── container_manager.go
│   ├── task-runner
│   │   └── runner.go
│   └── types
│       └── task.go
├── main.go
└── task.yaml

4 directories, 7 files

できます ここをクリック をクリックして、この例用に開発された完全なソース コードにアクセスします。 このガイドでは重要なスニペットを活用していますが、完全なコードは全体を通して文書化されていません。  

version: v0.0.1
tasks:
  - name: hello-gopher
    runner: busybox
    command: ["echo", "Hello, Gopher!"]
    cleanup: false
  - name: gopher-loops
    runner: busybox
    command:
      [
        "sh",
        "-c",
        "for i in `seq 0 5`; do echo 'gopher is working'; sleep 1; done",
      ]
    cleanup: false

 

タスクの定義

何よりもまず、タスク構造を定義する必要があります。 このタスクは、次の構造を持つ YAML 定義になります。

次の表では、タスク定義について説明します。

 

スクリーンショット 2022 07 21 で 5.17.30午後

 

タスク定義ができたので、同等の Go 構造体を作成しましょう。

SGoのトラクトは、型付きフィールドのコレクションです。 データをグループ化してレコードを形成する場合に便利です。 例えばこの Task タスク 構造体型には 、、 、 Runner Comman d および Cleanup Nam e田畑。

// internal/types/task.go

package types

// TaskDefinition represents a task definition document.
type TaskDefinition struct {
	Version string `yaml:"version,omitempty"`
	Tasks   []Task `yaml:"tasks,omitempty"`
}

// Task provides a task definition for gopher.
type Task struct {
	Name    string   `yaml:"name,omitempty"`
	Runner  string   `yaml:"runner,omitempty"`
	Command []string `yaml:"command,omitempty"`
	Cleanup bool     `yaml:"cleanup,omitempty"`
}

 

タスクランナーの作成

次に必要なのは、タスクを実行できるコンポーネントです。 これにはインターフェイスを使用します。 は、メソッド シグネチャの名前付きコレクションです。 この例のタスクランナーでは、weは単にそれを呼びます Runner 以下で定義します。

// internal/task-runner/runner.go

type Runner interface {
	Run(ctx context.Context, doneCh chan<- bool)
}

完了チャネル (完了Ch). これは、タスクを非同期で実行するために必要であり、このタスクが完了すると通知されます。

タスクランナーの完全な定義を見つけることができます ここは.ただし、この例では、コードの特定の部分を強調表示することに固執します。

// internal/task-runner/runner.go

func NewRunner(def types.TaskDefinition) (Runner, error) {
	client, err := initDockerClient()
	if err != nil {
		return nil, err
	}

	return &runner{
		def:              def,
		containerManager: cm.NewContainerManager(client),
	}, nil
}

func initDockerClient() (cm.DockerClient, error) {
	cli, err := client.NewClientWithOpts(client.FromEnv)
	if err != nil {
		return nil, err
	}

	return cli, nil
}

NewRunner 構造体のインスタンスを返し、 ランナー インターフェイス。 インスタンスは、Docker エンジンへの接続も保持します。 ザ initDockerClient 関数は、環境変数から Docker API クライアント インスタンスを作成することによって、この接続を初期化します。

デフォルトでは、この関数は Unix ソケット経由で HTTP 接続を作成します unix://var/run/docker.sock (デフォルトの Docker ホスト) をクリックします。 ホストを変更する場合は、 DOCKER_HOST 環境変数。 ザ FromEnv 環境変数を読み取り、それに応じて変更を加えます。

Run 以下に定義する関数は比較的基本的なものです。 タスクのリストをループして実行します。 また、という名前のチャネルも使用します。 taskDoneCh をクリックして、タスクがいつ完了したかを確認します。 受け取ったかどうかを確認することが重要です 完成です この関数から戻る前に、すべてのタスクからのシグナル。

// internal/task-runner/runner.go

func (r *runner) Run(ctx context.Context, doneCh chan<- bool) {
	taskDoneCh := make(chan bool)
	for _, task := range r.def.Tasks {
		go r.run(ctx, task, taskDoneCh)
	}

	taskCompleted := 0
	for {
		if <-taskDoneCh {
			taskCompleted++
		}

		if taskCompleted == len(r.def.Tasks) {
			doneCh <- true
			return
		}
	}
}

func (r *runner) run(ctx context.Context, task types.Task, taskDoneCh chan<- bool) {
	defer func() {
		taskDoneCh <- true
	}()

	fmt.Println("preparing task - ", task.Name)
	if err := r.containerManager.PullImage(ctx, task.Runner); err != nil {
		fmt.Println(err)
		return
	}

	id, err := r.containerManager.CreateContainer(ctx, task)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println("starting task - ", task.Name)
	err = r.containerManager.StartContainer(ctx, id)
	if err != nil {
		fmt.Println(err)
		return
	}

	statusSuccess, err := r.containerManager.WaitForContainer(ctx, id)
	if err != nil {
		fmt.Println(err)
		return
	}

	if statusSuccess {
		fmt.Println("completed task - ", task.Name)

		// cleanup by removing the task container
		if task.Cleanup {
			fmt.Println("cleanup task - ", task.Name)
			err = r.containerManager.RemoveContainer(ctx, id)
			if err != nil {
				fmt.Println(err)
			}
		}
	} else {
		fmt.Println("failed task - ", task.Name)
	}
}

 

内部 run 機能は、 runner.タスクを受け入れ、それを Docker コンテナーに変換します。 ある コンテナマネージャー Docker コンテナーの形式でタスクを実行します。

コンテナマネージャー

コンテナマネージャーは、次の責任を負います。

  • タスクの Docker イメージをプルする

  • タスクコンテナの作成

  • タスクコンテナの開始

  • コンテナーの完了を待機しています

  • コンテナの取り外し (必要な場合)

したがって、Goに関しては、以下に示すようにコンテナマネージャーを定義できます。

// internal/container-manager/container_manager.go
 
type ContainerManager interface {
	PullImage(ctx context.Context, image string) error
	CreateContainer(ctx context.Context, task types.Task) (string, error)
	StartContainer(ctx context.Context, id string) error
	WaitForContainer(ctx context.Context, id string) (bool, error)
	RemoveContainer(ctx context.Context, id string) error
}

type DockerClient interface {
	client.ImageAPIClient
	client.ContainerAPIClient
}

type ImagePullStatus struct {
	Status         string `json:"status"`
	Error          string `json:"error"`
	Progress       string `json:"progress"`
	ProgressDetail struct {
		Current int `json:"current"`
		Total   int `json:"total"`
	} `json:"progressDetail"`
}

type containermanager struct {
	cli DockerClient
}

 

containerManager インターフェイス というフィールドがあります cliDockerClient 種類。 インターフェイスには、Docker API の 2 つのインターフェイスが埋め込まれます。 ImageAPIClient そして ContainerAPIClient.Wこれらのインターフェースが必要ですか?

のために ContainerManager インターフェイス 正しく動作するには、Docker エンジンと API のクライアントとして機能する必要があります。 クライアントがイメージとコンテナーを効果的に操作するには、必要な API を提供する型である必要があります。 Docker API のコア インターフェイスを埋め込み、新しいインターフェイスを作成する必要があります。

initDockerClient 関数(上記の runner.go) は、これらの必要なインターフェイスをシームレスに実装するインスタンスを返します。 ドキュメントを確認する ここは を使用して、Docker クライアントの作成時に返される内容をよりよく理解できます。

その間、あなたは見ることができます コンテナマネージャの完全な定義 ここは.

手記: ここではコンテナマネージャのすべての機能を個別にカバーしていませんが、そうでなければブログが広すぎるでしょう。

エントリポイント

個々のコンポーネントについて説明したので、すべてを組み立てましょう main.go, これが私たちのエントリポイントです。 パッケージ main Goコンパイラに、パッケージを共有ライブラリではなく実行可能プログラムとしてコンパイルする必要があることを通知します。 ザ main() の関数 main package はプログラムのエントリ ポイントです。

// main.go

package main

func main() {
	args := os.Args[1:]

	if len(args) < 2 || args[0] != argRun {
		fmt.Println(helpMessage)
		return
	}

	// read the task definition file
	def, err := readTaskDefinition(args[1])
	if err != nil {
		fmt.Printf(errReadTaskDef, err)
	}

	// create a task runner for the task definition
	ctx := context.Background()
	runner, err := taskrunner.NewRunner(def)
	if err != nil {
		fmt.Printf(errNewRunner, err)
	}

	doneCh := make(chan bool)
	go runner.Run(ctx, doneCh)

	<-doneCh
}

 

これが何です 私たちのGoプログラムは:

  • 引数を検証します。

  • タスク定義を読み取ります

  • タスクランナーを初期化し、次にコンテナマネージャーを初期化します

  • ランナーから最終信号を受信するための完了チャネルを作成します

  • タスクを実行します

タスクシステムの構築

1)リポジトリのクローンを作成する

ソース コードは GitHub でホストされています。 次のコマンドを使用して 、リポジトリをローカルマシンにクローンします。

git clone https://github.com/dockersamples/gopher-task-system.git

 

2) Bタスクシステムの構築

ビルドする 命令 パッケージとその依存関係をコンパイルします。

go build -o gopher

3)タスクを実行する

gopherファイルを直接実行して、次のようにタスクを実行できます。

$ ./gopher run task.yaml

preparing task -  gopher-loops
preparing task -  hello-gopher
starting task -  gopher-loops
starting task -  hello-gopher
completed task -  hello-gopher
completed task -  gopher-loops

 

4)すべてのタスクコンテナを表示する  

コンテナーの完全な一覧は、 Docker デスクトップ内で表示できます。 ダッシュボード には、次の情報が明確に表示されます。 

画像2 2

5)CLI経由ですべてのタスクコンテナを表示する

または、 docker ps -a また、すべてのタスクコンテナを表示することもできます。

$ docker ps -a
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS                     PORTS     NAMES
396e25d3cea8   busybox   "sh -c 'for i in `se…"   6 minutes ago   Exited (0) 6 minutes ago             gopher-loops
aba428b48a0c   busybox   "echo 'Hello, Gopher…"   6 minutes ago   Exited (0) 6 minutes ago 

なお、 task.yaml cleanup フラグはに設定されています false 両方のタスクに対して。 これは、タスクの完了後にコンテナー リストを取得するために意図的に行いました。 これを true タスクコンテナを自動的に削除します。

シーケンス図

 

結論

Docker は、個々のコンテナーを構築、共有、および実行するためのソフトウェア開発ツールのコレクションです。 Docker SDK を使用すると、Docker ベースのアプリとソリューションをすばやく簡単に構築してスケーリングできます。 また、Dockerが内部でどのように機能するかをよりよく理解できます。 このような例をさらに共有し、Docker SDKで取り組むことができる他のプロジェクトをすぐに紹介することを楽しみにしています。

Docker SDK の活用をご自身で始めてみませんか? インストール手順、クイックスタートガイド、ライブラリ情報については、 ドキュメント を参照してください。

参照