C
Varlink
Podman

C アプリケーションから varlink の API を使って Container を操作する

※記事は全て個人の見解です。会社・組織を代表するものではありません。


はじめに

varlink の C 言語用のライブラリ libvarlinkを使ってみた、および、libvarlink を使って Container を操作してみたことの紹介です。


  • libvarlink を使って、varlink を使用するクライアントアプリケーションを実装します。

  • 作成したアプリケーションを使って、Container を操作します。

  • Container の操作には podman を使います。
    podman には varlink を使った APIが用意されており、API を用いてアプリケーションから Pod や Container の操作等が可能です。


環境


  • Fedora 29


準備


  • 必要なパッケージのインストール

]# dnf install libvarlink libvarlink-devel varlink-cli podman gcc jq


  • Podman Remote API Service を start

]# systemctl start io.podman.service

これで、API 通信用の socket が /run/podman/io.podman に作成されます。

varlink コマンドを実行して以下が確認できれば準備OK。

]$ varlink call -m unix:/run/podman/io.podman/io.podman.Ping

{
"ping": {
"message": "OK"
}
}

ちなみに、この varlink コマンドは Rust で実装されているようです。


クライアントアプリケーション実装と動作確認

/usr/include/varlink.h に記載されている varlink のライブラリ関数を使ってよろしくコーディング

(サンプルコードは記事の末尾に記載)して、コンパイル。

]$ gcc -lvarlink -o varlink-c varlink-c.c 

]# ./varlink-c
Usage: varlink-c -a varlink-address -m varlink-method [-p parameter]
Ex. varlink-c -a unix:/run/podman/io.podman -m io.podman.Ping
]#

できあがったバイナリ varlink-c には、API 通信用の socket、メソッド名、メソッドのパラメータを引数として渡します。

Podman の API は API.md に記載されています。

試しに func Ping を実行してみます。socket 名に unix:/run/podman/io.podman、メソッド名に io.podman.Ping を渡して

実行してみると・・・

]# ./varlink-c -a unix:/run/podman/io.podman -m io.podman.Ping | jq

{
"ping": {
"message": "OK"
}
}
]#

前述の varlink コマンドと同じ結果になり、うまく動作しているようです。

ちなみに 標準出力を jq コマンドに渡しているのは、JSON の処理が面倒くさくて jq 先生に頼っているためです。


Container 操作

さっそく Container を作ってみます。nginx の Container を func CreateContainer を使って作ります。

メソッド名は io.podman.CreateContainer、パラメータには以下の JSON を渡しています。

{

"create": {
"image": "nginx",
"command": [
"nginx",
"-g",
"daemon off;"
]
}
}

]# ./varlink-c -a unix:/run/podman/io.podman -m io.podman.CreateContainer -p "{\"create\": {\"image\" : \"nginx\", \"command\" : [\"nginx\", \"-g\", \"daemon off;\"] }}" | jq

{
"container": "66adaf4953a61061b99ac94d687a908e6cc72e0884e4301bdbf4eb3bc37b6190"
}
]#

ID が 66adaf4953a6 の nginx の Container が作成されました。

つづいて、この Container を動かしてみます。

]# ./varlink-c -a unix:/run/podman/io.podman -m io.podman.StartContainer -p "{\"name\": \"66adaf4953a6\"}"  | jq

{
"container": "66adaf4953a61061b99ac94d687a908e6cc72e0884e4301bdbf4eb3bc37b6190"
}

podman ps コマンドで確認してみると・・・

]# podman ps

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
66adaf4953a6 nginx nginx -g daemon o... 51 seconds ago Up 32 seconds ago stoic_jang
]#

ちゃんと動いているようです。


おわりに

私が調べた限りですが、libvarlink を使ったサンプルが見当たらなかったので、本記事が何らかの参考になれば幸いです。

varlink には Go、Rust、Python 用のライブラリがあるので、あえて C で書かなくてもいいよね、という話かも。。。

本記事ではクライアント側を実装してみましたが、libvarlink には API Server を実装するためのライブラリも用意されています。


サンプルコード

libvarlink のライブラリ関数の説明については、/usr/include/varlink.h のコメントを参照。


varlink-c.c

#include <varlink.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/epoll.h>

#define TIMEOUT 100

const char *progname = "varlink-c";

static long check_event(VarlinkConnection *connection)
{
struct epoll_event events;
int epollfd;
long nfds;
long ret = 0;

epollfd = epoll_create1(EPOLL_CLOEXEC);
if (epollfd == -1) {
fprintf(stderr, "ERR: epoll_create1: %s (errno: %d)\n", strerror(errno), errno);
return errno;
}

events.events = varlink_connection_get_events(connection);
events.data.ptr = connection;

ret = epoll_ctl(epollfd, EPOLL_CTL_ADD,
varlink_connection_get_fd(connection),
&events);
if (ret == -1) {
fprintf(stderr, "ERR: epoll_ctl: %s (errno: %d)\n", strerror(errno), errno);
ret = errno;
goto free_fd;
}

for (;;) {
nfds = epoll_wait(epollfd, &events, 1, TIMEOUT);
if (nfds == -1) {
fprintf(stderr, "ERR: epoll_wait: %s (errno: %d)\n", strerror(errno), errno);
ret = errno;
break;
} else if (!nfds)
continue;
else {
ret = varlink_connection_process_events(connection, events.events);
if (ret < 0)
fprintf(stderr, "ERR: varlink_connection_process_events: %s\n",
varlink_error_string(labs(ret)));
break;
}
}

free_fd:
close(epollfd);

return ret;
}

static long callback(VarlinkConnection *connection,
const char *error,
VarlinkObject *parameters,
uint64_t flags,
void *userdata)
{
VarlinkObject **out = userdata;
long ret = 0;

if (error) {
fprintf(stderr, "ERR: %s: %s\n", __func__, error);
ret = -1;
} else
*out = varlink_object_ref(parameters);

return ret;
}

static void usage(void)
{
fprintf(stderr, "Usage: %s -a varlink-address -m varlink-method [-p parameter]\n"
"Ex. %s -a unix:/run/podman/io.podman -m io.podman.Ping\n",
progname, progname);
exit(EXIT_FAILURE);
}

int main(int argc, char **argv)
{
VarlinkConnection *connection;
VarlinkObject *parameters;
VarlinkObject *out;
char *varlinkaddr = NULL;
char *varlinkmethod = NULL;
char *param;
int addr_flg = 0, method_flg = 0, param_flg = 0;
int status = EXIT_FAILURE;
long ret;
int c;
char *result = NULL;

while ((c = getopt(argc, argv, "a:m:p:")) != EOF)
switch (c) {
case 'a':
varlinkaddr = optarg;
addr_flg = 1;
break;
case 'm':
varlinkmethod = optarg;
method_flg = 1;
break;
case 'p':
param = optarg;
param_flg = 1;
break;
default:
usage();
}

if (!addr_flg || !method_flg)
usage();

ret = varlink_connection_new(&connection, varlinkaddr);
if (ret < 0) {
fprintf(stderr, "ERR: varlink_connection_new: ret: %s\n",
varlink_error_string(labs(ret)));
exit(status);
}

if (param_flg) {
ret = varlink_object_new_from_json(&parameters, param);
if (ret < 0) {
fprintf(stderr, "ERR: varlink_object_new_from_json: %s\n",
varlink_error_string(labs(ret)));
goto free_connection;
}
} else {
ret = varlink_object_new(&parameters);
if (ret < 0) {
fprintf(stderr, "ERR: varlink_object_new: %s\n",
varlink_error_string(labs(ret)));
goto free_connection;
}
}

ret = varlink_connection_call(connection, varlinkmethod, parameters, 0,
callback, &out);
if (ret < 0) {
fprintf(stderr, "ERR: varlink_connection_call: %s\n",
varlink_error_string(labs(ret)));
goto free_object;
}

ret = check_event(connection);
if (ret)
goto free_object;

ret = varlink_object_to_json(out, &result);
if (ret < 0) {
varlink_object_unref(out);
goto free_object;
}

fprintf(stdout, "%s\n", result);

free(result);

status = EXIT_SUCCESS;

varlink_object_unref(out);
free_object:
varlink_object_unref(parameters);
free_connection:
varlink_connection_free(connection);

exit(status);
}