Help us understand the problem. What is going on with this article?

Dockerの全コンテナで走っているプロセス一覧を取得する.

はじめに

この記事に関するgithubレポジトリはこちら

やったこと

dockerのコンテナ内で走っているプロセスを取得するコマンドはあるにはあるが,

Usage:  docker top CONTAINER [ps OPTIONS]

なので,一つのコンテナ内のプロセスしか得られない.
すべてのコンテナ内のプロセスを得ようと思ったら,上記のコマンドを繰り返す必要がある.
それでは面倒くさいので,全コンテナ内のプロセスを一覧表示するCLIツールを作った.

実際の挙動はこんな感じ.
スクリーンショット

この記事では,簡単にインストール方法を紹介したあと,コード解説をしようと思います.

インストール方法

ダウンロード&展開するだけ

wget -O - https://github.com/nomura-lab/dps/releases/download/v1.0.0/dps_1.0.0_linux_amd64.tar.gz | tar zxvf -

お好みでpathの通る場所に置いたりしてください.

使い方

本体のある場所で,

./dps

とすれば全コンテナ内のプロセス一覧が表示されます.

もし,このようなエラーが出た場合は,

panic: Error response from daemon: client version <x.xx> is too new. Maximum supported API version is <y.yy>

--apiversionのフラグを使ってください

./dps --apiversion <y.yy>

記事最初のスクリーンショットでは,--apiversion 1.40を用いています.

解説

使用したもの

このツールはgolangdocker clientパッケージを用いて開発しています.
また,出力に関しては
https://github.com/rgeoghegan/tabulate
こちらのパッケージを使わせて頂いています.

そして,cliツール化するのに,
https://github.com/urfave/cli
を使用し,配布には
https://github.com/goreleaser/goreleaser
を用いています.

正直,cliツール化して配布するのがこんなに簡単だとは思ってませんでした.

簡単にコード解説

このツールのメインの部分はこちらです.

ちなみに,goでdockerを操作するやり方については別記事で書いてるので良かったら見てください.

Go言語でdockerを操作する【イメージのpull,コンテナ作成,exec等】

では,肝になる部分を見ていきます.

dockerに接続する.

    var (
        cli *client.Client
        err error
    )
    if apiVersion == "default" {
        cli, err = client.NewClientWithOpts(client.FromEnv)
    } else {
        cli, err = client.NewClientWithOpts(client.WithVersion(apiVersion))
    }
    if err != nil {
        panic(err)
    }

    ctx := context.Background()

この部分でdockerに接続してます.(なんと表現していいか分からないので便宜上接続すると表現してます.)
ここでは,マシン上のdockerのバージョンが最新のclient APIのバージョンに対応していない可能性があるため,実行時に--apiversionで指定できるようにしています.
何も指定されなければ"default"が入ります.

 コンテナ一覧の取得

    containers, err := cli.ContainerList(ctx, types.ContainerListOptions{})
    if err != nil {
        panic(err)
    }

これでcontainersに起動しているコンテナ一覧が入ります.

それぞれのコンテナ内プロセスの取得

    //output用
    contents := make([][]string, 0)
    header := []string{"Image", "Container"}

    for _, container := range containers {
        processes, _ := cli.ContainerTop(ctx, container.ID, []string{"aux"})

        //header作成
        if len(header) == 2 {
            header = append(header, processes.Titles...)
        }

        for _, process := range processes.Processes {

            //イメージ名とコンテナ名の追加
            process = insertFirst(process, container.Names[0])
            process = insertFirst(process, container.Image)

            //Commandが長すぎる場合に省略
            if len(process[12]) > 20 {
                process[12] = process[12][:20] + "..."
            }

            //"Tty"の欄を削除
            process = remove(process, 8)

            contents = append(contents, process)
        }
    }

あるコンテナ内のプロセスは,

        processes, _ := cli.ContainerTop(ctx, container.ID, []string{"aux"})

で取得できます.
取得したプロセスを色々整形しつつcontentsにappendしていきます.
ちなみに,insertFirstはスライスの最初に要素を追加する関数で,removeはスライスの指定位置の要素を削除する関数です.

func insertFirst(slice []string, str string) []string {
    slice, slice[0] = append(slice[:1], slice[0:]...), str
    return slice
}

func remove(slice []string, pos int) []string {
    if pos >= len(slice) {
        return slice
    }
    return append(slice[:pos], slice[pos+1:]...)
}

あとはこれをheadercontentsを出力すれば完成です.
説明は省きます.

CLIツール化

せっかくなのでCLIツール化の部分も載せておきます.

package main

import (
    "log"
    "os"

    "github.com/nomura-lab/dps/api"
    "github.com/urfave/cli"
)

func main() {
    app := cli.NewApp()
    app.Name = "dps"
    app.Usage = "dps shows processes in each containers"

    app.Flags = []cli.Flag{
        cli.StringFlag{
            Name:  "apiVersion, apiversion",
            Value: "default",
            Usage: "API version",
        },
    }

    app.Action = func(c *cli.Context) error {
        api.RunDps(c.String("apiVersion"))
        return nil
    }

    err := app.Run(os.Args)
    if err != nil {
        log.Fatal(err)
    }

}

先程のコードを"github.com/nomura-lab/dps/api"でインポートし,api.RunDps(c.String("apiVersion"))で呼び出しています.

"github.com/urfave/cli"のおかげでかなり簡単にCLIツール化することができてます.
正直簡単に書けすぎて解説するところがないくらい.

golereaserで配布

配布もすごい簡単にできました.
これはすでに解説してる方がいるのでそちらを参照ということで.

goreleaser を使って Github Releases へ簡単デプロイ #golang

所感

世の中便利なパッケージやツールがたくさんあってちょっとしたものなら簡単に作れるのしゅごい

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away