TL; DR
# リソース名が長い!
$ k get replicasets
ERROR: too long! should be `kubectl get rs`
# オプション名が長い!
$ k get rs --namespace foo
ERROR: too long! should be `kubectl get rs -n foo`
# ヨシ!
$ k get rs -n foo
NAME DESIRED CURRENT READY AGE
nginx-76d6c9b8c 1 1 1 23h
はじめに
CKADは、k8sを利用するアプリケーション開発者向けの試験です。
最大の特徴として、実際にコマンドでクラスターのリソースを操作する実技試験であることが挙げられます。
さらに、他の方の受験記を見るとどうやら「設問が多く試験時間がギリギリ」なようです。
私も目下勉強中ですが、教材の練習問題の時点で間に合わずヒーヒー言っています。
(時間が足りない...せめて kubectl
のタイピングを1文字でも減らせれば...)
(そういえば、短縮コマンドがあったっけ...でもたくさんあって覚えられないよ...)
作ったもの
短縮コマンドが覚えられないなら、体に叩き込みましょう。短縮名を使わない限りエラーが出れば自然と身につくはずです。
このコマンドは kubectl
のラッパーとして動作し、長い引数を指定されるとエラーを吐きます。
インストール
インストールは go install
だけでokです。
$ go install github.com/syuparn/kuberta@latest
go: downloading github.com/syuparn/kuberta v0.1.0
続いて、エイリアスを登録します (kubectl
は k
で実行するのが貴族の嗜みです)。
alias k=kuberta
だらだらコマンドを打っていると叱ってくれます。
# リソース名が長い!😠
$ k get replicasets
ERROR: too long! should be `kubectl get rs`
# オプションが長い!😠
$ k get rs --namespace foo
ERROR: too long! should be `kubectl get rs -n foo`
# 😄
$ k get rs -n foo
NAME DESIRED CURRENT READY AGE
nginx-76d6c9b8c 1 1 1 23h
実装
コマンドをラップ
まずは、kubectl
をラップするCLIを作成します。今回はGoで開発しました。
仕組みは単純で、受け取ったコマンドライン引数を exec.Command
で kubectl
へ横流しします。
func Exec(args []string, w io.Writer) error {
if len(args) == 0 {
return Help(w)
}
// ...
// コマンドライン引数をそのままkubectlへ渡す
delegateToKubectl(args, w)
return nil
}
func delegateToKubectl(args []string, w io.Writer) error {
cmd := exec.Command("kubectl", args...)
cmd.Stdout = w
return cmd.Run()
}
引数バリデーションの仕組み
これだけではただの kubectl
なので、コマンド実行前にバリデーションチェックを追加します。
リソースの短縮名が使われているか
まずは、リソースの短縮名が使われているか確認します。
短縮名の取得
リソースの正式名称と短縮名の対応は kubectl api-resources
で確認できるので、この結果を愚直にパースします。
$ kubectl api-resources
NAME SHORTNAMES APIVERSION NAMESPACED KIND
bindings v1 true Binding
componentstatuses cs v1 false ComponentStatus
configmaps cm v1 true ConfigMap
endpoints ep v1 true Endpoints
events ev v1 true Event
limitranges limits v1 true LimitRange
namespaces ns v1 false Namespace
nodes no v1 false Node
...
コードが死ぬほど汚いです が、ネタコマンドなのでご愛敬
func GetResourceAliasMap() (map[string]string, error) {
cmd := exec.Command("kubectl", "api-resources", "--no-headers")
b, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("failed to get resource aliases: %w", err)
}
// 各行、各フィールドでsplitし[][]stringに整形
records := splitOutput(string(b))
// {名称: 短縮名} 形式のマップを生成
return parseResourceAliasMap(records)
}
func splitOutput(b string) [][]string {
spaces := regexp.MustCompile(`\s+`)
lines := strings.Split(b, "\n")
return lo.Map(lines, func(line string, _ int) []string {
return spaces.Split(line, -1)
})
}
func parseResourceAliasMap(records [][]string) (map[string]string, error) {
m := map[string]string{}
for _, record := range records {
if len(record) < 5 {
// 短縮名が定義されていないのでスキップ
continue
}
shortNames := strings.Split(record[1], ",")
name := record[0]
kind := record[4]
for _, shortName := range shortNames {
// 複数形
m[name] = shortName
// 単数形
m[strings.ToLower(kind)] = shortName
}
}
return m, nil
}
バリデーションチェック
次に、渡された引数が短縮名に置換可能かどうかを調べます。
func ValidateResourceNames(args []string, aliases map[string]string) error {
expectedArgs := make([]string, len(args))
copy(expectedArgs, args)
for i, arg := range args {
// 引数を短縮名に置換可能であれば置換
if alias, ok := aliases[arg]; ok {
expectedArgs[i] = alias
}
// 完全一致ではなくスラッシュの手前にリソース名を指定する場合(例 `service/foo`)もチェック
prefix := strings.Split(arg, "/")[0]
if alias, ok := aliases[prefix]; ok {
expectedArgs[i] = strings.Replace(arg, prefix, alias, 1)
}
}
// 1つでも置換されていれば、短縮名を使い忘れているのでエラー
if !reflect.DeepEqual(args, expectedArgs) {
return fmt.Errorf("too long! should be `kubectl %s`", strings.Join(expectedArgs, " "))
}
return nil
}
オプションの別名が使われているか
続いて、オプション --hoge
の短縮形が使われているか確認します。
短縮オプションの取得
指定されたコマンドの後ろに -h
を付けて、ヘルプを出力します。
Options:
以下にオプションの説明が表示されます。
$ k get po -h
Display one or many resources.
Prints a table of the most important information about the specified resources. You can filter the list using a label
selector and the --selector flag. If the desired resource type is namespaced you will only see results in your current
namespace unless you pass --all-namespaces.
...
Options:
-A, --all-namespaces=false:
If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even
if specified with --namespace.
--allow-missing-template-keys=true:
If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to
golang and jsonpath output formats.
--chunk-size=500:
Return large lists in chunks rather than all at once. Pass 0 to disable. This flag is beta and may change in
the future.
--field-selector='':
Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector
key1=value1,key2=value2). The server only supports a limited number of field queries per type.
-f, --filename=[]:
Filename, directory, or URL to files identifying the resource to get from a server.
...
また、全コマンドで共通して使えるコマンドは kubectl options
で確認できます。
$ kubectl options
The following options can be passed to any command:
--add-dir-header=false:
If true, adds the file directory to the header of the log messages (DEPRECATED: will be removed in a future
release, see
https://github.com/kubernetes/enhancements/tree/master/keps/sig-instrumentation/2845-deprecate-klog-specific-flags-in-k8s-components)
--alsologtostderr=false:
log to standard error as well as files (no effect when -logtostderr=true) (DEPRECATED: will be removed in a
future release, see
https://github.com/kubernetes/enhancements/tree/master/keps/sig-instrumentation/2845-deprecate-klog-specific-flags-in-k8s-components)
--as='':
Username to impersonate for the operation. User could be a regular user or a service account in a namespace.
オプションのパースはリソース名よりもごり押しです。-
で始まる行をパースし、{正式なオプション: 短縮オプション} の対応マップを作成します。
-f, --filename=[]:
(汚いのでコード略。詳細はこちら)
バリデーションチェック
リソース名のときと同じように、引数を短縮オプションに置換可能であればエラーを発生させます。
最後に、kubectl
を呼び出す直前にこれらのバリデーションチェックを入れれば完成です。
func Exec(args []string, w io.Writer) error {
if len(args) == 0 {
return Help(w)
}
// リソース名チェック
resourceAliases, err := GetResourceAliasMap()
if err != nil {
return fmt.Errorf("FATAL: failed to create resource alias map: %w", err)
}
err = ValidateResourceNames(args, resourceAliases)
if err != nil {
fmt.Fprintf(w, "ERROR: %s\n", err)
return nil
}
// オプションチェック
optionAliases, err := GetOptionAliasMap(args)
if err != nil {
return fmt.Errorf("FATAL: failed to create option alias map: %w", err)
}
err = ValidateOptions(args, optionAliases)
if err != nil {
fmt.Fprintf(w, "ERROR: %s\n", err)
return nil
}
delegateToKubectl(args, w)
return nil
}
おわりに
こんなの作ってる暇があったらCKADの勉強しろ