client-goでserviceのselectorに合致するpodを検索する
Kubernetesのクライアントであるclient-goを利用して,serviceのselectorに合致するpodを検索する方法を紹介します.コマンドラインではserviceの名前を受け取るが,実際の処理はpodに対して行う必要がある場合に活用できます.
クライアントの生成
準備として,コマンドライン引数からクライアントを生成します.genericclioptions.ConfigFlags
を使うと,kubectlの標準的なフラグを簡単に追加できます.
func run() error { // コマンドライン引数を解析する f := pflag.NewFlagSet("example", pflag.ContinueOnError) type cmdOptions struct { *genericclioptions.ConfigFlags } var o cmdOptions o.ConfigFlags.AddFlags(f) if err := f.Parse(os.Args[1:]); err != nil { return xerrors.Errorf("invalid flag: %w", err) } // kubeconfigからクライアントを生成する config, err := o.ConfigFlags.ToRESTConfig() if err != nil { return xerrors.Errorf("could not load the config: %w", err) } namespace, _, err := o.ConfigFlags.ToRawKubeConfigLoader().Namespace() if err != nil { return xerrors.Errorf("could not determine the namespace: %w", err) } clientset, err := kubernetes.NewForConfig(config) if err != nil { return xerrors.Errorf("could not create a client: %w", err) } }
実際のツール開発では,pflag
を直接利用するのではなく cobra.Command
を利用する方が便利かと思います.
serviceの取得とpodの検索
クライアントを利用してserviceを取得してみましょう.ここでは例として echoserver
というserviceを取得します.
serviceName := "echoserver" service, err := clientset.CoreV1().Services(namespace).Get(serviceName, metav1.GetOptions{}) if err != nil { return nil, "", xerrors.Errorf("could not find the service: %w", err) } log.Printf("Service %s found", service.Name)
serviceに対応するpodを検索するには,serviceのselectorを利用します.例えば,echoserver
サービスで app=echoserver
というselectorが定義されている場合,サービスに対応するpodには app=echoserver
というlabelが付いています.kubectlコマンドでserviceとpodの詳細を表示すると,selectorとlabelの関係がよくわかります.
% kubectl describe svc/echoserver Selector: app=echoserver % kubectl describe pod/echoserver-xxx-xxx Labels: app=echoserver
クライアントを利用してserviceのselectorに合致するpodを検索します.
// serviceで定義されているselectorをkey=value形式に変換する var selectors []string for k, v := range service.Spec.Selector { selectors = append(selectors, fmt.Sprintf("%s=%s", k, v)) } selector := strings.Join(selectors, ",") // selectorに合致するpodを検索する pods, err := clientset.CoreV1().Pods(namespace).List(metav1.ListOptions{LabelSelector: selector}) if err != nil { return nil, "", xerrors.Errorf("could not find the pods by selector %s: %w", selector, err) } log.Printf("Found pod(s): %+v", pods.Items)
これらのコードを実行すると,以下のような結果が表示されます.
2019/08/19 21:37:48 Service echoserver found 2019/08/19 21:37:48 Found pod(s): []Pod{...}
あとは,podのリストを利用して処理を行えばよいです.
まとめ
client-goを利用して,serviceのselectorに合致するpodを検索する方法を紹介しました.
EKS workerにSSMセッションマネージャで接続する
EC2インスタンスをプライベートサブネットに配置する場合,EC2インスタンスにSSHで接続するには踏み台が必要になります.Systems Managerのセッションマネージャーを利用すると,踏み台を経由せずにEC2インスタンスにSSHで接続できます.もはやターミナルも必要なく,WebブラウザがあればSSHで接続できます.とても便利ですね.
ところが,EKS workerのデフォルトAMI(amazon-eks-node-1.13-v20190701, ami-0fde798d17145fae1)にはSSMエージェントが入っていないため,デフォルトではセッションマネージャーを利用できません.どんな解決方法があるのか調べてみました.
EC2インスタンスの起動時にSSMエージェントをインストールする
userdataを利用して,EC2インスタンスの起動時にSSMエージェントをインストールします.terraform-aws-modules/eks/aws
を利用している場合は以下のようになります.
module "eks" { source = "terraform-aws-modules/eks/aws" version = "5.1.0" worker_groups = [ { name = "worker-group-1" # SSMエージェントをインストールする # https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html additional_userdata = "sudo yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm" }, ] # SSMに必要なポリシーをアタッチする workers_additional_policies = ["${aws_iam_policy.worker.arn}"] } resource "aws_iam_policy" "worker" { name = "worker" description = "IAM policy for Kubernetes workers" # https://docs.aws.amazon.com/systems-manager/latest/userguide/getting-started-create-iam-instance-profile.html policy = "${file("eks_worker_policy.json")}" }
additional_userdata
オプションを指定したworker groupにはSSMエージェントがインストールされます.インストールしたくない場合はオプションを外せばよいです.
この方法はお手軽ですが,以下のデメリットがあります.
- SSMエージェントをアップデートするには,EC2インスタンスにログインして手作業で行うか,EC2インスタンスをterminateする必要がある.(SSMエージェントを自動アップデートする設定ことも可能)
- SSMエージェントのプロセス監視やログ収集の仕組みがない.必要な場合は自分で作り込む.
DaemonSetでSSMエージェントを起動する
KubernetesのDaemonSetを利用して,workerでSSMエージェントを起動する方法があります.
KubernetesがSSMエージェントのコンテナを管理するので,以下のメリットがあります.
- SSMエージェントのPodが停止した場合は再起動されます.
- SSMエージェントのログはKubernetesを管理できます.
- SSMエージェントの削除やアップデートをkubectlで実行できる.(手作業ではなくGitOpsが可能になる)
DaemonSetのマニフェストを読むと非常に複雑なことをやっているので,セキュリティが気になる場合は目を通しておきましょう.また,現状ではSSMエージェントをバージョンアップするにはDockerイメージの再ビルドが必要です.
参考文献
GitLabのSAML SSO認証失敗とシステム時刻
とあるところで運用しているGitLab/Keycloakで発生した障害のメモ。
事象
GitLabにSAML SSOでログインしようとすると500エラーが表示される。GitLabとKeycloakの構成は以下で紹介した通りとなっている。
原因
GitLab Omnibusのコンテナで以下のログが出力されていた。
root@gitlab-5fbbf4957d-r5vrc:/# tail -f /var/log/gitlab/unicorn/unicorn_stdout.log E, [2019-08-13T02:07:13.128716 #11554] ERROR -- omniauth: (saml) Authentication failure! invalid_ticket: OneLogin::RubySaml::ValidationError, Current time is earlier than NotBefore condition (2019-08-13 02:07:13 UTC < 2019-08-13 02:07:14 UTC)
GitLabとKeycloakのシステム時刻がずれているため、SAML認証が失敗している。厳密には、KeycloakがGitLabに返したSAMLレスポンスに含まれる日時(NotBefore
)が未来になっていることが原因である。
GitLab Issuesを調べたところ、本件と同じ事象である OmniAuth/SAML Logins Fail with notbefore related error (#59466) · Issues · GitLab.org / GitLab Community Edition · GitLab が見つかった。
暫定対処
GitLabとKeycloakが同一のシステム時刻になるように構成を変更した。
恒久対処
https://docs.gitlab.com/ce/integration/saml.html#allowed_clock_drift のオプションが使えるはずだが未確認。
args: { allowed_clock_drift: 60 }