Skip to content

5-2. コンテナ用cgroupの追加 ​

それではいよいよ実装に移ります。コンテナ用のcgroupを追加してみましょう!

5-1で書いた通り、cgroupは全てファイルシステムとして管理されています。
なので、Namespaceやrootfsのように、特殊なsyscallを使う必要はなく、cgroupfsに対するファイル操作だけで扱うことができます。

cgroupを追加する ​

cgroupを追加するには、cgroupfsに対してディレクトリを作成するだけでOKです。
Goの標準ライブラリを用いて、cgroupの追加を実装してみましょう!

今回は、cgroupfsのルートディレクトリ直下の子cgroup、/sys/fs/cgroup/{コンテナ名}としてcgroupを追加します。
コンテナ名は関数の引数nameとして渡されます。
また、すでに同名のcgroupが存在する場合は、削除してから再作成してください。

ヒント1

ディレクトリの作成は、os.MkdirAll()を使うと良いでしょう。

ヒント2

ディレクトリの削除は、os.RemoveAll()を使うと良いでしょう。

ヒント3

5-1で書いた通り、cgroupのルートディレクトリは/sys/fs/cgroupです。
安全なパスの結合を行うために、filepath.Join()を使うと良いでしょう。

想定解答 ​

想定解答
go
const CgroupRoot = "/sys/fs/cgroup"

func SetupCgroup(name string, pid int, c CgroupConfig) error {
  // コンテナ用の子cgroup作成 (同名の子cgroupディレクトリがあれば削除)
  //  ディレクトリを作成した時点で、cgroupで操作可能なリソースに対応するファイルが生成される
  if err := os.RemoveAll(filepath.Join(CgroupRoot, name)); err != nil {
    return errors.WithStack(err)
  }
  if err := os.MkdirAll(filepath.Join(CgroupRoot, name), 0o755); err != nil {
    return errors.WithStack(err)
  }

  return nil
}

追加したcgroupを確かめる ​

cgroupfsにディレクトリを追加すると、5-1で書いた各種リソース制御用のファイルが自動的に生成されます。
実際に作られているか確かめてみましょう。
cgroupの操作にはroot権限が必要なので、sudo suを実行してrootになってからプログラムを実行してください。

シェルを2つ立ち上げてください。片方はプログラム実行用、もう片方は挙動確認用です。

プログラム実行用シェル

console
$ sudo su
# make run
go build -o main *.go
./main run bash
#

コンテナが立ち上がったら、挙動確認用シェルで/sys/fs/cgroup/{コンテナ名}ディレクトリを確かめてみましょう。

挙動確認用シェル

console
$ ls -l /sys/fs/cgroup/container/
total 0
-r--r--r-- 1 root root 0 Sep 12 05:56 cgroup.controllers
-r--r--r-- 1 root root 0 Sep 12 05:56 cgroup.events
-rw-r--r-- 1 root root 0 Sep 12 05:56 cgroup.freeze
--w------- 1 root root 0 Sep 12 05:56 cgroup.kill
-rw-r--r-- 1 root root 0 Sep 12 05:56 cgroup.max.depth
-rw-r--r-- 1 root root 0 Sep 12 05:56 cgroup.max.descendants
-rw-r--r-- 1 root root 0 Sep 12 05:56 cgroup.pressure
-rw-r--r-- 1 root root 0 Sep 12 05:56 cgroup.procs
-r--r--r-- 1 root root 0 Sep 12 05:56 cgroup.stat
-rw-r--r-- 1 root root 0 Sep 12 05:56 cgroup.subtree_control
-rw-r--r-- 1 root root 0 Sep 12 05:56 cgroup.threads
-rw-r--r-- 1 root root 0 Sep 12 05:56 cgroup.type
-rw-r--r-- 1 root root 0 Sep 12 05:56 cpu.idle
-rw-r--r-- 1 root root 0 Sep 12 05:56 cpu.max
... (省略)

このように、関連ファイルが自動的に生成されていることが確かめられるはずです。

cgroupにプロセスを追加する ​

cgroupを追加したら、次はコンテナのプロセスをそのcgroupに追加してみましょう。
あるcgroupの所属プロセスは、{cgroupディレクトリ}/cgroup.procsというファイルにPIDを書き込むことで追加できます。

コンテナになるべきプロセスのPIDは、関数の引数pidとして渡されてます。

ヒント

ファイルに書き込むには、os.WriteFile()を使うと良いでしょう。

想定解答 ​

想定解答
go
const CgroupRoot = "/sys/fs/cgroup"

func SetupCgroup(name string, pid int, c CgroupConfig) error {
  // コンテナ用の子cgroup作成 (同名の子cgroupディレクトリがあれば削除)
  //  ディレクトリを作成した時点で、cgroupで操作可能なリソースに対応するファイルが生成される
  if err := os.RemoveAll(filepath.Join(CgroupRoot, name)); err != nil {
    return errors.WithStack(err)
  }
  if err := os.MkdirAll(filepath.Join(CgroupRoot, name), 0o755); err != nil {
    return errors.WithStack(err)
  }

  // 今回コンテナにするプロセスをcgroupに追加
  if err := os.WriteFile(filepath.Join(CgroupRoot, name, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0o755); err != nil { 
    return errors.WithStack(err) 
  } 

  return nil
}

プロセスが追加されたことを確かめる ​

{cgroupディレクトリ}/cgroup.procsに実際にPIDが書き込まれているか確かめてみましょう。

プログラム実行用シェル

console
$ sudo su
# make run
go build -o main *.go
./main run bash
#

コンテナが立ち上がったら、挙動確認用シェルで/sys/fs/cgroup/{コンテナ名}ディレクトリを確かめてみましょう。

挙動確認用シェル

console
$ cat /sys/fs/cgroup/container/cgroup.procs
16747
16754

このように、PIDが2つ書き込まれていることが確かめられるはずです。
番号が小さい方が実際に書き込んだPID (mainバイナリのPID)、番号が大きい方がその子プロセス (bash) のPIDです。子プロセスのPIDは自動的に書き込まれています。