GeekFactory

int128.hatenablog.com

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エージェントがインストールされます.インストールしたくない場合はオプションを外せばよいです.

この方法はお手軽ですが,以下のデメリットがあります.

DaemonSetでSSMエージェントを起動する

KubernetesのDaemonSetを利用して,workerでSSMエージェントを起動する方法があります.

github.com

KubernetesがSSMエージェントのコンテナを管理するので,以下のメリットがあります.

  • SSMエージェントのPodが停止した場合は再起動されます.
  • SSMエージェントのログはKubernetesを管理できます.
  • SSMエージェントの削除やアップデートをkubectlで実行できる.(手作業ではなくGitOpsが可能になる)

DaemonSetのマニフェストを読むと非常に複雑なことをやっているので,セキュリティが気になる場合は目を通しておきましょう.また,現状ではSSMエージェントをバージョンアップするにはDockerイメージの再ビルドが必要です.

参考文献

GitLabのSAML SSO認証失敗とシステム時刻

とあるところで運用しているGitLab/Keycloakで発生した障害のメモ。

事象

GitLabにSAML SSOでログインしようとすると500エラーが表示される。GitLabとKeycloakの構成は以下で紹介した通りとなっている。

int128.hatenablog.com

原因

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
}