こんにちは住民! BPF仮想マシンは、Linuxカーネルの最も重要なコンポーネントの1つです。そのインテリジェントなアプリケーションにより、システムエンジニアは障害を見つけ、最も複雑な問題でも解決できます。カーネルの動作を監視および変更するプログラムの作成方法、カーネル内のイベントを監視するためのコードを安全に挿入できる方法などを学習します。 DavidCalaveraとLorenzoFontanaが、BPFのパワーを解き放つお手伝いをします。パフォーマンスの最適化、ネットワーキング、セキュリティに関する知識を広げます。 -BPFを使用して、Linuxカーネルの動作を追跡および変更します。 -カーネル内のイベントを安全に監視するためのコードを挿入します-カーネルを再コンパイルしたり、システムを再起動したりする必要はありません。 -C、Go、またはPythonで便利なコード例を使用します。 -BPFプログラムのライフサイクルを所有することにより、状況を管理します。
Linuxカーネルのセキュリティ、機能、およびSeccomp
BPFは、安定性、セキュリティ、または速度を損なうことなくカーネルを拡張する強力な方法を提供します。このため、カーネル開発者は、Seccomp BPFとも呼ばれるBPFプログラムでサポートされるSeccompフィルターを実装することにより、その汎用性を活用してSeccompのプロセス分離を改善することをお勧めします。この章では、Seccompとは何かとその適用方法について説明します。次に、BPFプログラムを使用してSeccompフィルターを作成する方法を学習します。その後、カーネルがLinuxセキュリティモジュール用に持っている組み込みのBPFフックを見てみましょう。
Linuxセキュリティモジュール(LSM)は、さまざまなセキュリティモデルの実装を標準化するために使用できる一連の機能を提供するプラットフォームです。LSMは、Apparmor、SELinux、Tomoyoなどのカーネルソースツリーで直接使用できます。
Linuxの機能について説明することから始めましょう。
機能
Linux機能の要点は、特定のタスクを実行するために非特権プロセスのアクセス許可を付与する必要があることですが、この目的のためにsuidを使用しないか、プロセスを特権化して、攻撃の可能性を減らし、プロセスが特定のタスクを実行できるようにします。たとえば、アプリケーションでプロセスをrootとして実行する代わりに、特権ポート(80など)を開く必要がある場合は、CAP_NET_BIND_SERVICE機能を指定するだけです。
main.goという名前のGoプログラムについて考えてみます。
package main
import (
"net/http"
"log"
)
func main() {
log.Fatalf("%v", http.ListenAndServe(":80", nil))
}
このプログラムは、ポート80(これは特権ポートです)でHTTPサーバーにサービスを提供します。通常、コンパイルの直後に実行します。
$ go build -o capabilities main.go
$ ./capabilities
ただし、root権限を付与していないため、このコードはポートをバインドするときにエラーをスローします。
2019/04/25 23:17:06 listen tcp :80: bind: permission denied
exit status 1
capsh(シェル制御ツール)は、特定の機能セットを備えたシェルを起動するツールです。
この場合、すでに述べたように、完全なルート権限を付与する代わりに、cap_net_bind_serviceをプログラムにすでに存在する他のすべてのものと一緒に有効にすることで、特権ポートバインディングを有効にできます。これを行うには、プログラムをcapshでラップします。
# capsh --caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' \
--keep=1 --user="nobody" \
--addamb=cap_net_bind_service -- -c "./capabilities"
このコマンドについて少し理解しましょう。
- capsh-capshをシェルとして使用します。
- --caps = 'cap_net_bind_service + eip cap_setpcap、cap_setuid、cap_setgid + ep'-ユーザーを変更する必要があるため(rootとして実行したくない)、cap_net_bind_serviceと、ユーザーIDをrootからnobodyに実際に変更する機能(cap_setuidとcap_setgid)を指定します。 ..。
- --keep=1 — , root.
- --user=«nobody» — , , nobody.
- --addamb=cap_net_bind_service — root.
- — -c "./capabilities" — .
— , , execve(). , , , , .
--capsオプションで機能を指定した後、+ eipが何を意味するのか疑問に思われるかもしれません。これらのフラグは、機能を指定するために使用されます
。-アクティブ化する必要があります(p)。
-アプリケーションで利用可能(e);
-子プロセス(i)によって継承できます。
cap_net_bind_serviceを使用したいので、eフラグを使用して使用する必要があります。次に、コマンドでシェルを起動します。これにより、機能バイナリが起動し、iフラグでマークする必要があります。最後に、pを使用して機能をアクティブ化する必要があります(UIDを変更せずにこれを実行しました)。cap_net_bind_service + eipのように見えます。
ssで結果を確認できます。ページに収まるように出力を少し縮小しますが、0以外の関連するポートとユーザーID(この場合は65,534)が表示されます。
# ss -tulpn -e -H | cut -d' ' -f17-
128 *:80 *:*
users:(("capabilities",pid=30040,fd=3)) uid:65534 ino:11311579 sk:2c v6only:0
この例ではcapshを使用しましたが、libcapを使用してシェルを作成できます。詳細については、man 3libcapを参照してください。
プログラムを作成するとき、開発者は実行時にプログラムに必要なすべての機能を事前に知らないことがよくあります。さらに、これらの機能は新しいバージョンで変更される可能性があります。
プログラムの機能をよりよく理解するために、cap_capableカーネル関数のkprobeを設定するBCC対応ツールを使用できます。
/usr/share/bcc/tools/capable
TIME UID PID TID COMM CAP NAME AUDIT
10:12:53 0 424 424 systemd-udevd 12 CAP_NET_ADMIN 1
10:12:57 0 1103 1101 timesync 25 CAP_SYS_TIME 1
10:12:57 0 19545 19545 capabilities 10 CAP_NET_BIND_SERVICE 1
cap_capableカーネル関数の1行のkprobeでbpftraceを使用することで、同じことを実現できます。
bpftrace -e \
'kprobe:cap_capable {
time("%H:%M:%S ");
printf("%-6d %-6d %-16s %-4d %d\n", uid, pid, comm, arg2, arg3);
}' \
| grep -i capabilities
kprobeの後にプログラムの機能がアクティブ化された場合、これは次のようなものを出力します。
12:01:56 1000 13524 capabilities 21 0
12:01:56 1000 13524 capabilities 21 0
12:01:56 1000 13524 capabilities 21 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 10 1
5番目の列はプロセスに必要な機能であり、この出力には非監査イベントが含まれているため、すべての非監査チェックが表示され、最後に監査フラグ(出力の最後の1つ)が1に設定された必要な機能が表示されます。関心のあるCAP_NET_BIND_SERVICEは、include / uapi / linux /ability.hファイルのカーネルソースコードで、識別子が10の定数として定義されています。
/* Allows binding to TCP/UDP sockets below 1024 */
/* Allows binding to ATM VCIs below 32 */
#define CAP_NET_BIND_SERVICE 10<source lang="go">
runCやDockerなどのコンテナを非特権モードで実行するために、実行時に機能が利用されることがよくありますが、ほとんどのアプリケーションの実行に必要な機能のみが許可されます。アプリケーションが特定の機能を必要とする場合、Dockerはそれらに--cap-addを提供できます。
docker run -it --rm --cap-add=NET_ADMIN ubuntu ip link add dummy0 type dummy
このコマンドは、コンテナにCAP_NET_ADMIN機能を提供します。これにより、dummy0インターフェイスを追加するようにネットワークリンクを構成できます。
次のセクションでは、フィルタリングなどの機能の使用方法を示しますが、独自のフィルターをプログラムで実装できる別の方法を使用します。
Seccomp
SeccompはSecureComputingの略で、開発者が特定のシステム呼び出しを除外できるようにするLinuxカーネルに実装されたセキュリティの層です。 SeccompはLinuxの機能に匹敵しますが、特定のシステム呼び出しを処理する機能により、それよりもはるかに柔軟性があります。
SeccompとLinuxの機能は相互に排他的ではなく、両方のアプローチの恩恵を受けるために一緒に使用されることがよくあります。たとえば、プロセスにCAP_NET_ADMIN機能を付与したいが、acceptおよびaccept4システム呼び出しをブロックしてソケット接続を受け入れないようにしたい場合があります。
Seccompフィルタリング方法は、SECCOMP_MODE_FILTERモードで動作するBPFフィルターに基づいており、システム呼び出しフィルタリングはパケットの場合と同じ方法で実行されます。
Seccompフィルターは、PR_SET_SECCOMP操作を介してprctlを使用してロードされます。これらのフィルターは、seccomp_data構造で表される各Seccompパッケージに対して実行されるBPFプログラムの形式です。この構造には、参照アーキテクチャ、システム呼び出し中のプロセッサ命令へのポインタ、およびuint64として表される最大6つのシステム呼び出し引数が含まれています。
これは、seccomp_data構造がlinux /seccomp.hファイルのカーネルソースからどのように見えるかを示しています。
struct seccomp_data {
int nr;
__u32 arch;
__u64 instruction_pointer;
__u64 args[6];
};
この構造からわかるように、システム呼び出し、その引数、または両方の組み合わせでフィルタリングできます。
各Seccompパケットを受信した後、フィルターは処理を実行して最終決定を行い、カーネルに次に何をするかを指示する必要があります。最終決定は、戻り値(ステータスコード)の1つで表されます。
-SECCOMP_RET_KILL_PROCESS-このために実行されないシステム呼び出しをフィルタリングした直後のプロセス全体の終了。
-SECCOMP_RET_KILL_THREAD-システム呼び出しをフィルタリングした直後の現在のスレッドの終了。これが原因で実行されません。
-SECCOMP_RET_KILL-SECCOMP_RET_KILL_THREADのエイリアス。下位互換性のために残されています。
--SECCOMP_RET_TRAP —システム呼び出しが無効になり、SIGSYS(Bad System Call)信号が呼び出し元のタスクに送信されます。
-SECCOMP_RET_ERRNO-システム呼び出しは実行されず、SECCOMP_RET_DATAフィルターの戻り値の一部がerrnoとしてユーザースペースに渡されます。エラーの原因に応じて、異なるerrno値が返されます。エラー番号は次のセクションにリストされています。
--SECCOMP_RET_TRACE--ptraceに通知するために使用されます--PTRACE_O_TRACESECCOMPは、このプロセスを確認および制御するためにシステム呼び出しが行われたときにインターセプトします。トレーサーが接続されていない場合、エラーが返され、errnoが-ENOSYSに設定され、システム呼び出しは実行されません。
-SECCOMP_RET_LOG-システム呼び出しが許可され、ログに記録されます。
-SECCOMP_RET_ALLOW-システム呼び出しは単に許可されます。
ptraceは、traceeと呼ばれるプロセスにトレースメカニズムを実装するためのシステム呼び出しであり、プロセスの実行を監視および制御する機能を備えています。トレースプログラムは、実行に効果的に影響を与え、トレースメモリレジスタを変更できます。Seccompのコンテキストでは、SECCOMP_RET_TRACEステータスコードによってトリガーされたときにptraceが使用されるため、トレーサーはシステム呼び出しの実行を防ぎ、独自のロジックを実装できます。
Seccompエラー
Seccompを使用すると、さまざまなエラーが発生することがあります。これらのエラーは、SECCOMP_RET_ERRNOタイプの戻り値で識別されます。エラーを報告するために、seccompシステム呼び出しは0ではなく-1を返し
ます。次のエラーが発生する可能性があります。
-EACCESS-呼び出し元はシステム呼び出しを行うことができません。これは通常、CAP_SYS_ADMIN特権がないか、no_new_privsがprctlで設定されていないために発生します(詳細は後で説明します)。
-EFAULT-渡された引数(seccomp_data構造内の引数)に有効なアドレスがありません。
--EINVAL-ここには4つの理由が考えられます。-
要求された操作が不明であるか、現在の構成のカーネルでサポートされていません。
-指定されたフラグは、要求された操作に対して無効です。
-操作にはBPF_ABSが含まれますが、指定されたオフセットに問題があり、seccomp_data構造のサイズを超える可能性があります。
-フィルターに渡される命令の数が最大値を超えています。
--ENOMEM —プログラムを実行するのに十分なメモリがありません。
--EOPNOTSUPP —操作は、アクションがSECCOMP_GET_ACTION_AVAILで使用可能であることを示しましたが、カーネルは引数の戻りをサポートしていません。
--ESRCH —別のストリームの同期に問題がありました。
-ENOSYS-SECCOMP_RET_TRACEアクションにトレーサーがアタッチされていません。
prctlは、ユーザースペースプログラムが、バイトシーケンス、スレッド名、セキュアコンピューティングモード(Seccomp)、特権、パフォーマンスイベントなど、プロセスの特定の側面を操作(設定および取得)できるようにするシステム呼び出しです。
Seccompは、サンドボックステクノロジーのように聞こえるかもしれませんが、そうではありません。Seccompは、ユーザーがサンドボックスメカニズムを開発できるようにするユーティリティです。次に、Seccompシステム呼び出しによって直接呼び出されるフィルターを使用してカスタム対話プログラムを作成する方法を見てみましょう。
BPFSeccompフィルターの例
ここでは、前述の2つのアクションを組み合わせる方法を示します。
-SeccompBPFプログラムを記述します。これは、行われた決定に応じて異なるリターンコードを持つフィルターとして使用されます。
--prctlを使用してフィルターをロードします。
まず、標準ライブラリとLinuxカーネルのヘッダーが必要です。
#include <errno.h>
#include <linux/audit.h>
#include <linux/bpf.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <unistd.h>
この例を試す前に、カーネルがCONFIG_SECCOMPおよびCONFIG_SECCOMP_FILTERをyに設定してコンパイルされていることを確認する必要があります。実稼働マシンでは、次のようにテストできます
cat /proc/config.gz| zcat | grep -i CONFIG_SECCOMP
。残りのコードは2つの部分からなるinstall_filter関数です。最初の部分には、BPFフィルタリング命令のリストが含まれています。
static int install_filter(int nr, int arch, int error) {
struct sock_filter filter[] = {
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, (offsetof(struct seccomp_data, arch))),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3),
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, (offsetof(struct seccomp_data, nr))),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1),
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (error & SECCOMP_RET_DATA)),
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW),
};
命令は、linux /filter.hファイルで定義されているBPF_STMTおよびBPF_JUMPマクロを使用して設定されます。
手順を見ていきましょう。
--BPF_STMT(BPF_LD + BPF_W + BPF_ABS(offsetof(struct seccomp_data、arch)))-システムはBPF_Wという単語の形式でBPF_LDをロードおよび累積し、パケットデータは固定オフセットBPF_ABSに配置されます。
-BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K、arch、0、3)-BPF_Kアキュムレータ定数のアーキテクチャ値がarchと等しいかどうかをBPF_JEQを使用してチェックします。含まれている場合は、オフセット0で次の命令にジャンプします。そうでない場合は、archが一致しないため、オフセット3(この場合)でジャンプしてエラーをスローします。
--BPF_STMT(BPF_LD + BPF_W + BPF_ABS(offsetof(struct seccomp_data、nr)))-固定オフセットBPF_ABSに含まれるシステム呼び出し番号である単語BPF_Wの形式でBPF_LDをダウンロードして累積します。
--BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K、nr、0、1)-システム呼び出し番号をnr変数の値と比較します。それらが等しい場合は、次のステートメントに進み、システム呼び出しを許可しません。それ以外の場合は、SECCOMP_RET_ALLOWを使用してシステム呼び出しを有効にします。
--BPF_STMT(BPF_RET + BPF_K、SECCOMP_RET_ERRNO |(エラーおよびSECCOMP_RET_DATA))-プログラムをBPF_RETで終了し、その結果、err変数の数値を使用してSECCOMP_RET_ERRNOエラーを発行します。
--BPF_STMT(BPF_RET + BPF_K、SECCOMP_RET_ALLOW)-プログラムをBPF_RETで終了し、SECCOMP_RET_ALLOWを使用してシステム呼び出しを実行できるようにします。
SECCOMP ISCBPF
コンパイルされたELFオブジェクトまたはJITでコンパイルされたCプログラムの代わりに命令のリストが使用される理由を疑問に思うかもしれません。
これには2つの理由があります。
•最初に、SeccompはeBPFではなくcBPF(クラシックBPF)を使用します。これは、レジスタがなく、例に示すように、最後の計算結果を格納するためのアキュムレータのみを使用することを意味します。
•次に、Seccompは、BPF命令の配列へのポインターを直接取得します。私たちが使用したマクロは、プログラマーにとって便利な形式でこれらの命令を指定するのに役立つだけです。
このアセンブリを理解するためにさらにヘルプが必要な場合は、同じことを行う疑似コードを検討してください。
if (arch != AUDIT_ARCH_X86_64) {
return SECCOMP_RET_ALLOW;
}
if (nr == __NR_write) {
return SECCOMP_RET_ERRNO;
}
return SECCOMP_RET_ALLOW;
socket_filter構造でフィルターコードを定義した後、コードと計算されたフィルター長を含むsock_fprogを定義する必要があります。このデータ構造は、将来のプロセスの作業を宣言するための引数として必要です。
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
.filter = filter,
};
install_filter関数で実行する必要があるのは、プログラム自体をダウンロードすることだけです。これを行うには、prctlを使用し、オプションとしてPR_SET_SECCOMPを使用して、セキュアコンピューティングモードに入ります。次に、sock_fprogタイプのprog変数に含まれているSECCOMP_MODE_FILTERを使用してフィルターをロードするようにモードに指示します。
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
perror("prctl(PR_SET_SECCOMP)");
return 1;
}
return 0;
}
最後に、install_filter関数を使用できますが、その前にprctlを使用して現在の実行にPR_SET_NO_NEW_PRIVSを設定し、子プロセスが親よりも多くの特権を取得する状況を回避する必要があります。これにより、root権限がなくても、install_filter関数でprctlを次のように呼び出すことができます。
これで、install_filter関数を呼び出すことができます。X86-64アーキテクチャに関連するすべての書き込みシステム呼び出しをブロックし、すべての試行をブロックする許可を与えましょう。フィルタをインストールした後、最初の引数を使用して実行を続行します。
int main(int argc, char const *argv[]) {
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
perror("prctl(NO_NEW_PRIVS)");
return 1;
}
install_filter(__NR_write, AUDIT_ARCH_X86_64, EPERM);
return system(argv[1]);
}
始めましょう。clangまたはgccのいずれかを使用してプログラムをコンパイルできます。いずれの場合も、特別なオプションなしでmain.cファイルをコンパイルするだけです。
clang main.c -o filter-write
前述のように、プログラムのすべてのエントリをブロックしました。これをテストするには、何かを出力するプログラムが必要です-lsは良い候補のようです。これは彼女が通常どのように振る舞うかです:
ls -la
total 36
drwxr-xr-x 2 fntlnz users 4096 Apr 28 21:09 .
drwxr-xr-x 4 fntlnz users 4096 Apr 26 13:01 ..
-rwxr-xr-x 1 fntlnz users 16800 Apr 28 21:09 filter-write
-rw-r--r-- 1 fntlnz users 19 Apr 28 21:09 .gitignore
-rw-r--r-- 1 fntlnz users 1282 Apr 28 21:08 main.c
完璧に!シェルプログラムは次のようになります。テストするプログラムを最初の引数として渡すだけです。
./filter-write "ls -la"
このプログラムを実行すると、完全に空の出力が生成されます。ただし、straceを使用して、何が起こっているかを確認できます。
strace -f ./filter-write "ls -la"
作業の結果は大幅に短縮されますが、対応する部分は、レコードがEPERMエラー(構成したものと同じ)でブロックされていることを示しています。これは、書き込みシステム呼び出しにアクセスできないため、プログラムが何も出力しないことを意味します。
[pid 25099] write(2, "ls: ", 4) = -1 EPERM (Operation not permitted)
[pid 25099] write(2, "write error", 11) = -1 EPERM (Operation not permitted)
[pid 25099] write(2, "\n", 1) = -1 EPERM (Operation not permitted)
これで、Seccomp BPFがどのように機能するかを理解し、それを使用して何ができるかについての良いアイデアが得られました。しかし、フルパワーを使用するために、cBPFの代わりにeBPFで同じことをしたいと思いませんか?
eBPFプログラムについて考えるとき、ほとんどの人は、管理者権限でプログラムを作成してロードしているだけだと思います。このステートメントは一般的に当てはまりますが、カーネルはさまざまなレベルでeBPFオブジェクトを保護するための一連のメカニズムを実装しています。これらのメカニズムは、BPFLSMトラップと呼ばれます。
トラップBPFLSM
システムイベントのアーキテクチャに依存しない監視を提供するために、LSMはトラップの概念を実装します。フックコールは技術的にはシステムコールに似ていますが、システムに依存せず、インフラストラクチャと統合されています。 LSMは、抽象化レイヤーが異なるアーキテクチャーでシステム呼び出しを処理するときに発生する問題を回避するのに役立つ新しい概念を提供します。
この記事の執筆時点では、カーネルにはBPFプログラムに関連付けられた7つのフックがあり、SELinuxはそれらを実装する唯一の組み込みLSMです。
フックのソースコードは、include / linux /security.hファイルのカーネルツリーにあります。
extern int security_bpf(int cmd, union bpf_attr *attr, unsigned int size);
extern int security_bpf_map(struct bpf_map *map, fmode_t fmode);
extern int security_bpf_prog(struct bpf_prog *prog);
extern int security_bpf_map_alloc(struct bpf_map *map);
extern void security_bpf_map_free(struct bpf_map *map);
extern int security_bpf_prog_alloc(struct bpf_prog_aux *aux);
extern void security_bpf_prog_free(struct bpf_prog_aux *aux);
それらのそれぞれは、実行のさまざまな段階で呼び出されます。--
security_bpf-実行されたBPFシステム呼び出しの初期チェックを実行します。
--security_bpf_map-カーネルがマップのファイル記述子をいつ返すかをチェックします。
--security_bpf_prog-カーネルがeBPFプログラムのファイル記述子をいつ返すかを確認します。
--security_bpf_map_alloc-BPFマップ内のセキュリティフィールドが初期化されているかどうかを確認します。
--security_bpf_map_free-BPFマップ内のセキュリティフィールドがクリアされているかどうかを確認します。
--security_bpf_prog_alloc-セキュリティフィールドがBPFプログラム内で初期化されているかどうかを確認します。
--security_bpf_prog_free-BPFプログラム内でセキュリティフィールドがクリアされているかどうかを確認します。
これらすべてを見ると、LSM BPFインターセプターの背後にある考え方は、すべてのeBPFオブジェクトを保護し、適切な特権を持つユーザーだけがマップやプログラムで操作を実行できるようにすることであることがわかります。
概要
セキュリティは、保護したいものすべてに万能の方法で適用できるものではありません。さまざまなレベルでさまざまな方法でシステムを保護できることが重要です。信じられないかもしれませんが、システムを保護する最善の方法は、さまざまな位置からさまざまなレベルの保護を編成して、1つのレベルのセキュリティ低下によりシステム全体へのアクセスが妨げられるようにすることです。カーネル開発者は、さまざまなレイヤーとタッチポイントのセットを提供してくれました。レイヤーとは何か、およびBPFプログラムを使用してレイヤーを操作する方法について十分に理解していただけたと思います。
著者について
David Calaveraは、NetlifyのCTOです。彼はDockerサポートに勤務し、Runc、Go、BCCツール、およびその他のオープンソースプロジェクトの開発に貢献してきました。 Dockerプロジェクトでの彼の仕事とDockerプラグインエコシステムの開発で知られています。デビッドはフレームグラフが大好きで、常にパフォーマンスの最適化に努めています。
Lorenzo Fontanaは、Sysdigのオープンソース開発チームの一員であり、主に、カーネルモジュールとeBPFを介してコンテナランタイムセキュリティと異常検出を提供するCloud Native ComputingFoundationプロジェクトであるFalcoに関与しています。彼は、分散システム、ソフトウェア定義のネットワーキング、Linuxカーネル、およびパフォーマンス分析に情熱を注いでいます。
この本についての詳細は、上で見つけることができます»出版社のウェブサイト
»目次
»抜粋
については居住者クーポンで25%割引- Linuxの
書籍の紙のバージョンの支払いの際に、電子書籍を電子メールで送信されます。