<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Nikos Fotiou - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Nikos Fotiou - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/jp/search-labs/author/nikos-fotiou</link>
    </image>
    <link>https://www.elastic.co/jp/search-labs/author/nikos-fotiou</link>
    <atom:link href="https://www.elastic.co/jp/search-labs/rss/author/nikos-fotiou.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[jp]]></language>
    <lastBuildDate>Fri, 18 Sep 2026 23:38:09 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Kubernetesでの依存関係管理]]></title>
    <description><![CDATA[Renovate CLIとArgoワークフローを使用してKubernetesの依存関係管理を効率化する方法。]]></description>
    <content:encoded><![CDATA[<p>以下は、Kubernetes、Argo Workflows、Argo Events、Renovate CLIを使用して、更新を自動化し、Common Vulnerabilities and Exposures（CVE）に迅速に対処し、何千ものリポジトリ全体で新しいパッケージバージョンを効率的に伝播するセルフホスト型の依存関係管理プラットフォームを構築した方法です。</p><h2><strong>Elasticでの依存関係管理</strong></h2><p>Elasticでは、プライベートとパブリックの両方で、数百、数千ものリポジトリを管理する必要があります。重大なCVEが発見された場合、どのリポジトリが脆弱であるかを即座に回答して対処する必要があります。どれくらい早くパッチを当てられるでしょうか。セキュリティとは別に、生産性に関する質問も出てきます。手動のタスクにあまり時間をかけずに、新しいパッケージバージョンのリリースを、それに依存するすべてのリポジトリに迅速に広めるにはどうすればよいでしょうか。</p><p>依存関係管理の方法を探す最初のきっかけは、<a href="https://www.elastic.co/blog/reducing-cves-in-elastic-container-images">CVE削減</a>のための自動更新を備えた安全な基盤を確立する必要性でした。依存関係管理に関するソリューションを慎重に検討した後、まずセルフホスト型のインフラストラクチャーの作業を開始しました。私たちは独自のKubernetesクラスターを使用して、Mend Renovate Community Self-Hostedを実行していました。ユーザーがセルフサービスでアクセスできる依存関係管理プラットフォームを提供するというアイデアがありました。</p><p>最初の実験が成功したため、より多くのチームが私たちのプラットフォームを導入し、日常のリポジトリのライフサイクルにおける更新やCVEパッチ適用に使用するようになりました。この展開は非常に速く、私たちはすぐにセルフマネージドインストールの限界に達しました。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc99617fc3eed538d/6a170ea9964cea459d08bc67/e14d9f98d4eccaa08a335d5bd23d88e5debbb344-1600x1103.png" alt="Elasticでの依存関係管理" /><h3><strong>課題：多数のリポジトリを持つ大規模な組織で依存関係管理プラットフォームを拡張するにはどうすればよいでしょうか。</strong></h3><p>当社の依存関係管理プラットフォームは、一度に1つのリポジトリを処理しており、シーケンシャルな処理モデルでは、当社の所有する多数のリポジトリに対応できませんでした。依存関係管理ツールの<strong>単一のインスタンス</strong>で拡大し続けるリポジトリのリストを処理するという概念に問題があることは、すでに認識していました。リポジトリはキュー内に留まり、場合によっては何時間も待機することがありました。リポジトリの50%以上は毎日処理されていませんでした。つまり、当社のリポジトリの50%以上がスキャン間でを24時間以上待っていたということです。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0d205fd379e3c07a/6a170eab961e691e1fc4cfca/45ade5bda08f82bed0b3d0d3736cbd6f056e7a4e-1312x816.jpg" alt="依存関係管理の問題" /><p>大規模なリポジトリでは、コードベースのサイズが大きく、PRが複数オープンしているため、ボトルネックが大きくなります。GitHub Webhookイベントによりこのシーケンスが中断されました。スキャンのタイミングが予測できないため、Automergeの信頼性が低下しました。スキャンの頻度についてはユーザーと約束していましたが、それを果たすことができませんでした。</p><h3><strong>社内で構築するという決定：Elastic独自のスケールとセキュリティのニーズに対応</strong></h3><p>商用オプション、具体的には<strong>MendのRenovate Self-Hosted Enterprise Self-Hosted版</strong>も検討しましたが、Elastic社内ではいくつかの主要な取り組みが進行中でした。</p><p>社内プラットフォームを構築するという当社の決定は、Elastic の特定の譲れない要件を満たすには、徹底的にカスタマイズされたソリューションしかないという認識に基づいていました。</p><ol><li><p><strong>内部開発者プラットフォームへの投資：</strong>当時、私たちはすでに内部開発者プラットフォームに多額の投資を行っていました。それぞれのサービスをこれに適合させる方法について議論し、設計していました。つまり、依存関係管理プラットフォームの独自のルールと実践をテストドライブするというニーズがあり、それに加えて、新しいガイドラインが導入されることになり、イベントに先立ってプラットフォームを設計したいと考えていました。</p></li><li><p><strong>ネイティブ統合とワークフローのカスタマイズ：</strong>社内ツールや社内プロセスとの簡単な統合が必要で、例えば、Service Catalog（Backstage）を使用して構成をコードとして一元管理したいと考えていました。Backstageの使用に関しては、特定のニーズがあり、当社のプラットフォームと互換性を持たせたいと考えていました。したがって、Renovate Self-Hosted APIをBackstageの自動化と併用することは可能ですが、これでは当社の内部プロセスを完全にカバーすることはできません。</p></li><li><p><strong>Elastic特有の徹底したセキュリティ対策：</strong>当社の厳格なセキュリティコンプライアンスには、当社のエコシステムに合わせた特注のセキュリティメカニズムが必要でした。<a href="https://entro.security/blog/how-elastic-scaled-secrets-nhi-security-elastics-playbook-from-visibility-to-automation/">「非人間的アイデンティティ」の使用の強化</a>に取り組んでいました。このアクセス強化の仕組みにより、GitHub への認証を行う非標準の手段は、この内部実装をサポートしていない市販のツールでは機能しなくなります。当社のワークフローには、親子ワークフローの秘密の暗号化パターンを実装し、一時的な使い捨てのGitHubトークンを使用することが含まれていました。社内で構築することが、これらの独自のセキュリティレイヤーを組み込み、複雑なマルチクラウド環境全体の攻撃対象領域を最小限に抑える唯一の実用的な方法でした。</p></li></ol><h2><strong>解決策：依存関係管理のためのワークフローオーケストレーション</strong></h2><p>解決策の構築は、既に使用している依存関係管理ツールを基に構築し、それを置き換えたり他のソリューションを探したりするのではなく、その上に構築することから始まりました。その可能性の兆しはあり、その柔軟性は組織全体のさまざまなニーズにとって重要です。さまざまなソリューションを検討しましたが、最終的に決め手となったのは、カバーしなければならない大きくて時に特殊なニーズでした。私たちは、各リポジトリが独自に処理され、ボトルネックを解消して成長に備えられる、信頼性が高くスケーラブルな依存関係管理プラットフォームを構築することを決定しました。</p><p>プラットフォームは次の3つのコア原則に従って設計しました。</p><h3><strong>1. 並列処理</strong></h3><p>各リポジトリに独自の依存関係管理処理環境が与えられます。キューはなくなります。同時実行性は、消費するリソースの数によってのみ制限されます。また、GitHubでレート制限を受けないようにスマートな分散スケジューリングを適用しました。</p><h3><strong>2．セルフサービス可能</strong></h3><p>Service Catalog（Backstage）を使用して、新しいリポジトリを自動的にオンボードして管理します。独自のリソース定義を使用して、エンドユーザーにリポジトリの処理頻度を選択するオプション、スケジュールに割り当てるリソースの量、何らかの理由で処理をオンまたはオフにするオプションを提供します。ユーザーのニーズが進化し、新しいインストールに慣れてきたら、そのようにしてさらに多くのオプションを追加していく予定です。</p><h3><strong>3. シークレットのスコープと名前空間の分離の縮小</strong></h3><p>セキュリティを強化するために、各ワークフローの開始時に生成される一時的なGitHubトークンを依存関係管理ポッドに提供します。さらに、ワークロードを特定の名前空間に分離して、必要なシークレットのみが提供されるようにします。Kubernetes RBACを使用して、各依存関係管理ワークフローでアクセスできるシークレットを制御します。また、暗号化を使用して、親ワークフローから子ワークフローにGitHubトークンを伝播します。</p><p>Kubernetesを使用してプラットフォームを再構築し、Kubernetesのパワーを活用しました。Argo Workflowsはプロセスのロジックを強化し、Renovate CLIはリポジトリを一度に1つずつスキャンして処理するように設定されています。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3548539ab52fbb79/6a170eac0c48573da601ab26/5560ed20e2bd9ecdd574a9c835126d12b24c332f-1600x1157.png" alt="Kubernetesにおける依存関係管理ワークフローの概要" /><p><strong>ここで素晴らしい点は</strong>、実績のあるオープンソースプロジェクトを独自の方法で使用し、すべてのプロジェクトに新しい実用的な例を提供すると同時に、開発速度を増幅し、チームのCVE削減を強化していることです。</p><h2><strong>依存関係管理アーキテクチャー：4つのマイクロサービス</strong></h2><p>このプラットフォームは、次の4つのカスタムビルドコンポーネントで構成されています。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6451ff19da4db511/6a170eaec1e8a562e0f88378/2b3d4046c05bb261e45d40c59f864eb51fb9eaa9-1217x1600.png" alt="Kubernetesにおける依存関係管理のコンポーネント" /><h3><strong>ワークフローオペレーター（Go/Kubebuilder）</strong></h3><p>3つのカスタムリソース定義（CRD）を通じてワークフローライフサイクルを管理するKubernetesオペレーター：</p><ul><li><p><strong>RepoConfig CRD：</strong>リポジトリ設定のための信頼できる唯一の情報源。</p></li></ul><p>RepoConfigはオペレーターで次のように定義されています。</p>// RepoConfig is the Schema for the repoconfigs API
type RepoConfig struct {
	metav1.TypeMeta `json:",inline"`

	// metadata is a standard object metadata
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty,omitzero"`

	// spec defines the desired state of RepoConfig
	// +required
	Spec RepoConfigSpec `json:"spec"`

	// status defines the observed state of RepoConfig
	// +optional
	Status RepoConfigStatus `json:"status,omitempty,omitzero"`
}<p>RepoConfigのインスタンスは次のようになります。</p>apiVersion: workflows.elastic.co/v1
kind: RepoConfig
metadata:
  generation: 3
  name: elastic-test-repo
  namespace: dependency-management-operator
spec:
  owner: group:my-team
  renovate:
    config:
      resourceGroup: SMALL
      runFrequency: 4h
    enabled: true
  repository: elastic/test-repo<ul><li><p><strong>親CRD：</strong>スケジュールされたスキャンのCronWorkflowを管理します。</p></li></ul><p>親コントローラの調整ループ内では、ワークフロー設定が作成され、最新の状態に保たれ、必要に応じて削除されることを確認します。</p><p>まず、ワークフローのグローバル設定を取得します。</p>func (r *ParentReconciler) reconcileSubResources(ctx context.Context, req ctrl.Request, parent *workflowsv1.Parent) error {
	logger := logf.FromContext(ctx)
	logger.Info("Reconcile SubResources for Parent", "name", req.NamespacedName)
	wfSet := workflowsettings.WorkflowSettings{
		RunFrequency:   parent.Spec.RunFrequency,
		ResourceGroups: "parent",
	}<p>mutexのconfigmapが最新であることを確認し、類似したワークフローが同時に動作しないようにします:</p>	cfMngr := resources.NewConfigMapManager(r.Client, r.Scheme, r.OperatorConfig.ParentNamespace)
	err := cfMngr.CreateOrUpdateSyncMutexConfigmap(ctx, fmt.Sprintf("%s%s", r.OperatorConfig.ResourcesPrefix, r.OperatorConfig.SyncMutexCfgMapName), strings.TrimPrefix(parent.Spec.Repository, "elastic/"), r.OperatorConfig.SemaphoreConcurrencyLimit)<p>そして、Cronワークフローとワークフローテンプレートを作成または更新する構造体であるワークフローマネージャーを作成します。</p>	wfMngr := resources.NewArgoWorkflowManager(r.Client,
		r.Scheme,
		curateResourceName(
			strings.ReplaceAll(parent.Spec.Repository, "/", "-"),
		),
		parent.Namespace,
		"parent-workflow",
		false).
		WithOrganization(r.OperatorConfig.GitHubOrg).
		WithRepoName(parent.Spec.Repository).
		Init(true, true).
		WithPrefix(r.OperatorConfig.ResourcesPrefix).
		WithWfTemplateName(r.OperatorConfig.ParentWorkflowTemplate).
		WithResources(wfSet.GetResourceCategory()).
		WithSchedule(wfSet.GetCronSchedule()).
		WithImagePullSecrets([]corev1.LocalObjectReference{{
			Name: r.OperatorConfig.WorkflowImagePullSecrets,
		}}).
		AddArgument(true, true, "extra_cli_args").
		SetArgument(true, false, "extra_cli_args", "none").
		AddTemplate(resources.NewParentDAGTemplateInstance()).
		AddTemplate(resources.NewWorkflowsTemplateInstance("check-child-workflows", r.OperatorConfig.WorkflowImagePullPolicy, r.OperatorConfig.WorkflowNodeSelector)).
		AddTemplate(resources.NewWorkflowsTemplateInstance("security", r.OperatorConfig.WorkflowImagePullPolicy, r.OperatorConfig.WorkflowNodeSelector)).
		AddTemplate(resources.NewWorkflowsTemplateInstance("submit-child-workflow", r.OperatorConfig.WorkflowImagePullPolicy, r.OperatorConfig.WorkflowNodeSelector))
	wfMngr.OverWriteCommand("submit-child-workflow", r.OperatorConfig.ChildNamespace)
	wfMngr.OverwriteWfTemplateName("parent-wftmpl")
	wfMngr.AddSynchronization(fmt.Sprintf("%s%s", r.OperatorConfig.ResourcesPrefix, r.OperatorConfig.SyncMutexCfgMapName), "{{workflow.parameters.repo_name}}")
	err = wfMngr.CreateOrUpdateCronWorkflow(ctx)
	if err != nil {
		return fmt.Errorf("failed to create or update cron workflow: %w", err)
	}
	err = wfMngr.CreateOrUpdateWorkflowTemplate(ctx)
	if err != nil {
		return fmt.Errorf("failed to create or update workflow template: %w", err)
	}
	return nil<ul><li><p><strong>子CRD：</strong>リポジトリごとのリソースを使用してWorkflowTemplatesを管理します。</p></li></ul><p>子コントローラにーは親と同様の調整義務がありますが、今回は親ワークフローによってトリガーされる子名前空間内のワークフローテンプレートを担当します。</p>func (r *ChildReconciler) reconcileSubResources(ctx context.Context, req ctrl.Request, child *workflowsv1.Child) error {
	logger := logf.FromContext(ctx)
	logger.Info("Reconcile SubResources for Child", "name", req.NamespacedName)
	wfSet := workflowsettings.WorkflowSettings{
		ResourceGroups: child.Spec.ResourceCategory,
	}
	wfMngr := resources.NewArgoWorkflowManager(r.Client,
		r.Scheme,
		curateResourceName(
			strings.ReplaceAll(child.Spec.Repository, "/", "-"),
		),
		child.Namespace,
		"runner",
		true).
		Init(false, true). // only manage workflow template
		WithPrefix(r.OperatorConfig.ResourcesPrefix).
		WithSuffix("-child-wftmpl").
		WithRepoName(child.Spec.Repository).
		WithOrganization(r.OperatorConfig.GitHubOrg).
		WithResources(wfSet.GetResourceCategory()). // will override resources of presets if set
		WithImagePullSecrets([]corev1.LocalObjectReference{{
			Name: r.OperatorConfig.WorkflowImagePullSecrets,
		}}).
		AddTemplate(resources.NewWorkflowsTemplateInstance("runner", r.OperatorConfig.WorkflowImagePullPolicy, r.OperatorConfig.WorkflowNodeSelector)).
		AddArgument(false, true, "repo_full_name").
		AddArgument(false, true, "repo_name").
		AddArgument(false, true, "encrypted_token").
		AddArgument(false, true, "extra_cli_args")
	wfMngr.OverWriteCommand("runner", r.OperatorConfig.ChildNamespace)
	err := wfMngr.CreateOrUpdateWorkflowTemplate(ctx)
	if err != nil {
		return fmt.Errorf("failed to create or update workflow template: %w", err)
	}
	return nil
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta735156ba6e370ef/6a170eaf7d8d6706fd70e7e4/7ac70492a1266ba02cb8afbafc5a486cb38a0edc-1600x1290.png" alt="Kubernetesにおける依存関係管理のワークフロー" /><p>マルチコントローラーパターンでは明確な分離が実現されます。RepoConfigコントローラーはオンボーディング／オフボーディングを処理し、親コントローラーはスケジュールを管理し、子コントローラーは実行テンプレートを処理します。</p><h3><strong>GitHuイベントゲートウェイ（Go）</strong></h3><p>GitHubのwebhookを受信し、署名を検証し、組織／リポジトリでフィルタリングし、Argo Eventsにルーティングするセキュアなwebhookプロキシです。依存関係ダッシュボードのインタラクション、PRイベント、パッケージの更新に対応する10個の異なるセンサーを構築しました。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7e748ceb93c13a5a/6a170eb1a6c2b908f8e797b4/4828625456cbd6efa8020a20f10d23f294f98a02-1306x1600.png" alt="Kubernetesにおける依存関係ダッシュボードのインタラクション" /><p>このゲートウェイは、以下の方法でGitHub Appsとの統合を可能にします。</p><ul><li><p>セキュリティのため、受信したGitHub webhook署名を検証しています。</p></li><li><p>有効なイベントを、すべての関連ヘッダーと認証とともにArgo Events EventSourceに転送します。</p></li><li><p>また、EventSourceにAuthSecretを設定し、これを転送されるリクエストのBearerヘッダーとして提供します。</p></li><li><p>ログ、メトリクス、再試行ロジックを提供します。</p></li></ul><p>各GitHubイベントリクエストに対して様々な検証を行います。</p><p>以下のHTTP属性が存在することを確認します。</p>// ValidateRequestMethod checks if the request method is POST.
func ValidateRequestMethod(r *http.Request) error {
	if r.Method != http.MethodPost {
		return fmt.Errorf("method not allowed, only POST is accepted")
	}
	return nil
}

// ValidateRequiredHeaders checks for required GitHub headers.
func ValidateRequiredHeaders(r *http.Request) error {
	eventType := r.Header.Get("X-GitHub-Event")
	deliveryID := r.Header.Get("X-GitHub-Delivery")
	signature := r.Header.Get("X-Hub-Signature-256")
	if eventType == "" || deliveryID == "" || signature == "" {
		return fmt.Errorf("missing required GitHub headers")
	}
	return nil
}

// ValidateUserAgent checks that the User-Agent header starts with GitHub-Hookshot/
func ValidateUserAgent(r *http.Request) error {
	userAgent := r.Header.Get("User-Agent")
	if !strings.HasPrefix(userAgent, "GitHub-Hookshot/") {
		return fmt.Errorf("invalid User-Agent")
	}
	return nil
}<p>また、各リクエストの署名とその構成も検証します。</p>// ValidateSignature verifies the GitHub webhook signature.
func ValidateSignature(r *http.Request, secret string) ([]byte, error) {
	payload, err := GitHub.ValidatePayload(r, []byte(secret))
	if err != nil {
		return nil, fmt.Errorf("invalid GitHub signature: %w", err)
	}
	return payload, nil
}

// ValidateAllowedOwner checks if the organization login is in the allowed organizations list.
func ValidateAllowedOwner(payload []byte, allowedGitHubOrganizations []string) (string, error) {
	var orgLogin string
	var payloadMap map[string]any
	if err := json.Unmarshal(payload, &amp;payloadMap); err == nil {
		if orgObj, ok := payloadMap["organization"].(map[string]any); ok {
			if login, ok := orgObj["login"].(string); ok {
				orgLogin = login
			} else if name, ok := orgObj["name"].(string); ok {
				orgLogin = name
			}
		}
	}
	if !slices.Contains(allowedGitHubOrganizations, orgLogin) {
		return orgLogin, fmt.Errorf("organization login not allowed")
	}
	return orgLogin, nil
}<p>最後に、イベントの種類に基づいてArgo Eventsにルーティングします。</p>	// Map eventType to Argo `EventSource` path
	var endpoint string
	switch eventType {
	case "push":
		endpoint = "/push"
	case "issues":
		endpoint = "/issues"
	case "pull_request":
		endpoint = "/pull-requests"
	default:
		slog.Info("Ignoring unhandled event type", "event_type", eventType, "delivery_id", deliveryID)
		w.WriteHeader(http.StatusOK)
		_,  = w.Write([]byte("ok"))
		return
	}
	forwardURL := h.config.ArgoEventSourceForwardURL + endpoint<p>Argo Events側では、10個のセンサーがArgo Events EventBusで新しいイベントを監視します。</p>apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
  name: {{ .Values.sensors.packageUpdateOnDefaultBranch.name }}
  namespace: {{ .Release.Namespace }}
spec:
  eventBusName: {{ .Values.eventBus.name }}<p>次に、スクリプトは各センサーのロジックを適用します。</p>script: |
          local e = event
          if not e or not e.body or not e.body.repository then
            return false
          end

          -- e.g., "refs/heads/main"
          local ref = e.body.ref
          local default_branch = e.body.repository.default_branch
          if not ref or not default_branch then
            return false
          end

          local expected = "refs/heads/" .. default_branch
          if ref ~= expected then
            return false
          end

        {{- if .Values.sensors.packageUpdateOnDefaultBranch.packageFiles }}
          patterns = { {{- range $i, $f := .Values.sensors.packageUpdateOnDefaultBranch.packageFiles }}{{ if $i }}, {{ end }}"{{ $f }}"{{- end }} }
        {{- end }}

          local function anyMatch(path)
            if type(path) ~= "string" then return false end
            for _, pat in ipairs(patterns) do
              -- match filename at repo root, or anywhere under subdirs
              if path:match(pat) or path:match(".+/" .. pat) then
                return true
              end
            end
            return false
          end

          local function filesContainPackage(paths)
            if type(paths) ~= "table" then return false end
            for _, p in ipairs(paths) do
              if anyMatch(p) then return true end
            end
            return false
          end

          -- Inspect all commits (GitHub includes added/modified/removed lists)
          local commits = e.body.commits
          if type(commits) ~= "table" then
            -- Fallback: some payloads include only head_commit
            commits = {}
            if type(e.body.head_commit) == "table" then
              table.insert(commits, e.body.head_commit)
            end
          end

          for _, c in ipairs(commits) do
            if filesContainPackage(c.added) or filesContainPackage(c.modified) or filesContainPackage(c.removed) then
              return true
            end
          end

          return false<h3><strong>Backstage Syncer（Go）</strong></h3><p>このプロセスでは、Service Catalog（Backstage）に対してRepository Real Resource Entitiesのポーリングを行い、それらをRepoConfig CRDに変換し、プラットフォームを設定変更と同期させます。変更は3分以内に適用されます。</p>repoMap := make(map[string]map[string]interface{})
			for i := range entities {
				entity := &amp;entities[i]
				if entity.Spec.Type != "GitHub-repository" {
					continue
				}

				implRaw, err := json.Marshal(entity.Spec.Implementation)
				if err != nil {
					logger.Error("Failed to marshal implementation", "error", err)
					continue
				}

				var implMap map[string]interface{}
				err = json.Unmarshal(implRaw, &amp;implMap)
				if err != nil {
					logger.Error("Failed to unmarshal implementation map", "error", err)
					continue
				}
				var repoName string
				if specMap, ok := implMap["spec"].(map[string]interface{}); ok {
					if repo, ok := specMap["repository"].(string); ok {
						repoName = repo
					}
				}
				if repoName == "" {
					continue
				}

				var workflowsRaw []byte
				if v, ok := implMap["spec"].(map[string]interface{}); ok {
					if r, ok := v["renovate"]; ok {
						workflowsRaw,  = json.Marshal(r)
					} else {
						workflowsRaw = []byte(`{}`)
					}
				} else {
					workflowsRaw = []byte(`{}`)
				}

				var workflowsWithDefaults schema.WorkflowsMetadata
				err = json.Unmarshal(workflowsRaw, &amp;rworkflowsWithDefaults)
				if err != nil {
					logger.Error("Failed to unmarshal workflows config", "error", err)
					continue
				}

				workflowsMap := map[string]interface{}{
					"enabled":        workflowsWithDefaults.Enabled,
					"require_pr":     workflowsWithDefaults.RequirePr,
					"resource_group": string(workflowsWithDefaults.ResourceGroup),
					"run_frequency":  string(workflowsWithDefaults.RunFrequency),
				}
				repoMap[repoName] = map[string]interface{}{
					"renovate": workflowsMap,
					"owner":    entity.Spec.Owner,
				}
			}
			logger.Info("Fetched GitHub Repository data from Backstage", "repository_count", len(repoMap), "status_code", resp.StatusCode)<p>最後に、そのデータをRepoConfigインスタンスに書き込みます。</p><h3><strong>ワークフローベース（混合：JavaScript、Go、Helm）</strong></h3><p>基盤レイヤーには、Helmチャート、JavaScript設定、暗号化サポート付きのRenovate CLI用のGoラッパー、Alpineパッケージ用のカスタムAPKインデクサーが含まれています。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4c1b5b840854ddf5/6a170eb47d8d67694e70e7e8/908d19278face3ce1119dbee9146c1264b6e2f30-1600x873.png" alt=" Kubernetesにおける依存関係管理の基礎コンポーネント" /><h2><strong>セルフサービス構成</strong></h2><p>チームはBackstageを通じてリポジトリを宣言的に設定します。</p>spec:
  renovate:
    enabled: true
    config:
      resourceGroup: LARGE      # SMALL | MEDIUM | LARGE  
      runFrequency: "0 */4 * * *"  # Every 4 hours<p>リソースグループはリポジトリサイズに基づいてCPUとメモリを割り当てます。</p><ul><li><p><strong>SMALL:</strong> 500m CPU、1Giメモリ。</p></li><li><p><strong>MEDIUM:</strong> 1000m CPU、2Giメモリ。</p></li><li><p><strong>LARGE:</strong> 2000m CPU、4Giメモリ。</p></li></ul><p>構成はバージョン管理され、監査可能で、自動的に適用されます。</p><h2><strong>親子パターン</strong></h2><p>実行モデルは親子ワークフローパターンを使用しています。</p><ul><li><p><strong>親ワークフロー：</strong>スケジュールに従って実行される軽量のCronワークフロー。シークレットを暗号化し、スキャンを実行するかどうかを決定し、子プロセスに構成を渡します。</p></li><li><p><strong>子ワークフロー：</strong>Renovate CLIが実行される一時的なポッド。リソースを動的に割り当て、シークレットを単独で復号し、完了後に終了します。</p></li></ul><p>この分離により、セキュリティ（親レベルで暗号化されたシークレット）、リソースの最適化（親は最小限のリソースを使用）、拡張性（子が並行して実行）が提供されます。</p><h2><strong>結果</strong></h2><h3><strong>パフォーマンスの変化</strong></h3><ul><li><p><strong>変更前：</strong>一度に1つのリポジトリで、一部のリポジトリは、おそらく1日以上、1日あたり1,000回未満のスキャンでも処理されませんでした。</p></li><li><p><strong>変更後：</strong>100以上の同時スキャン、通常は8,000件のスキャンと1日あたり最大10,000件の記録スキャン。制限は、当社が費やすリソースの量とGitHubのレート制限の扱い方のみです。</p></li></ul><h3><strong>費用対効果</strong></h3><p>奇妙に聞こえるかもしれませんが、1 日に8,000件のポッドを実行すると、同じ結果を達成するために 1 つの長時間実行ポッドを実行するよりもはるかに安価に同じ結果を得ることができます。</p><p>以前の設定では、単一のインスタンスを実行していて、調子が良い日には500～600回のスキャンを実行していました。同時に、異なる種類のリポジトリが同じポッドで実行されることから、最大のものに合わせてポッドのサイズを設定する必要がありました。そのサイズは、現在の8つのCPUと16GBのメモリを搭載する特大モデルよりもはるかに大きくなります。</p><p>現在の日次出力を満たすには、単一のポッドなら12日間実行する必要があります。それでは、12日間稼働する単一のポッドのコストを、毎日稼働する8,000個の「MEDIUM」サイズのポッドのコストと比較すると、新しい設計は同じスキャン出力に対してはるかに効率的です。</p><p>メトリック</p><p>シナリオ A（ワークフロー）</p><p>シナリオB（長時間実行される単一のポッド）</p><p>セットアップ</p><p>8,000ポッド（1 vCPU／2GB）</p><p>1 pod (8 vCPU / 16 GB)*</p><p>期間</p><p>それぞれ10分</p><p>12日間連続</p><p>総作業時間</p><p>1,333計算時間</p><p>288計算時間</p><p>総コスト</p><p>65.83ドル</p><p>$113.75</p><p>しかし、ワークロードのデフォルト設定が「SMALL」に設定されていることを考慮に入れましょう。大多数は0.5 CPUと1G RAMで正常に実行されており、規模を増やす変更する必要があるのはごく一部です。ワークロードの60％が「SMALL」で実行され、30％が「MEDIUM」で実行され、10％が「LARGE」で実行されている場合（これが実際に近い値です）に何が起こるかを見てみましょう。</p><p>メトリック</p><p>シナリオA（混合群）</p><p>シナリオB（長時間実行）</p><p>戦略</p><p>8,000ポッド（混合サイズ）</p><p>1 pod (8 vCPU / 16 GB)*</p><p>期間</p><p>それぞれ10分</p><p>12日間連続</p><p>総コスト</p><p>$52.66</p><p>$113.75</p><p>節約額</p><p>$61.09（54%安価）</p><p>—</p><p>同じ出力に対して、現在の設定ではるかに費用対効果に優れていることがわかります。</p><h3><strong>強化されたセキュリティ</strong></h3><ul><li><p>一時的なGitHubトークン（数分間の公開と数日間の公開の比較）。</p></li><li><p>ロールベースアクセス制御（RBAC）境界による名前空間の分離。</p></li><li><p>親ワークフロー内の保存時のシークレットの暗号化。</p></li><li><p>直接のVaultアクセスを削除。</p></li></ul><h3><strong>予測可能なパフォーマンス</strong></h3><p>スキャン頻度が保証されれば、最終的にサービスレベル目標（SLO）を設定できます。Automergeは確実に動作します。チームは、プラットフォームが約束どおりの成果をもたらすと信頼しています。</p><h2><strong>重要なアーキテクチャ上の決定</strong></h2><p>ここでは、プラットフォームの外観を形作る重要な設計上の決定事項をいくつか紹介します。</p><ul><li><p><strong>親子ワークフローが必要な理由</strong></p></li></ul><p><strong>多層防御</strong>戦略を実施するためにこのパターンを採用しました。価値の高い認証情報（GitHubアプリのシークレットなど）を専用のロックダウンされた名前空間に制限することで、<strong>RBAC</strong>を使用して、一時的な実行ポッドが機密データに恣意的にアクセスできないようにします。最近のサプライチェーンの脆弱性（例えば、<strong>"Shai Hulud"</strong>継続的インテグレーション/継続的デリバリー[CI/CD]攻撃）は、動的スクリプトを実行するランタイム環境を認証情報ストアから分離することの重要性を実証しています。</p><p>同時に、この分離により、<strong>きめ細かなリソースの最適化</strong>が可能になります。「親」ワークフローは軽量のオーケストレーターとして機能し、最小限のフットプリントで動作します。一方、「子」ワークフローは計算集約型の依存関係スキャンを処理します。この分離により、各レイヤーに個別の調整ロジックを適用し、スケジューリングとセキュリティインフラ（親）の管理制御を維持しながら、実行パラメータ（子）の制御をユーザーに許可することで、<strong>ライフサイクル管理</strong>を簡素化できます。</p><ul><li><p><strong>セルフサービス可能な理由</strong></p></li></ul><p>リポジトリ構成のボトルネックとなる当社のチームを排除することは重要な要件でした。私たちの使命は、多様なユースケースをサポートできるスケーラブルな<strong>セルフサービスプラットフォーム</strong>を設計することでした。リポジトリの膨大な量を考慮すると、すべての構成変更の<strong>ゲートキーパー</strong>として機能することは持続不可能であると認識しました。代わりに、私たちは「レール」（インフラストラクチャと<strong>ガードレール</strong>）を提供しながら、ユーザーが「列車」（実行とカスタマイズ）を運転できるようにするという支援の哲学を採用しました。こうした<strong>チームの自律性</strong>へのシフトが、ユーザーがシステムをそれぞれの具体的な業務ニーズに合わせてカスタマイズできるようにすることで、生産性を大幅に向上させると信じています。</p><ul><li><p><strong>Kubernetes Operatorパターンを選択した理由</strong></p></li></ul><p>上記で述べたように、基本的な設計原則は、プラットフォームが完全に<strong>セルフサービス可能</strong>であることを確保することでした。ユーザーの意図（スキャンの切り替え、スケジューリング頻度の調整、ランタイムリソース制限の調整など）を捉え、それらの変更を基盤となるワークフローに瞬時に反映する自動化されたメカニズムが必要でした。将来の要件を予測し、システムは簡単に<strong>拡張可能</strong>である必要もありました。</p><p>これを実現するために、カスタムの<strong>依存関係管理Kubernetes Operator</strong>を開発しました。設定のインターフェースとして<strong>CRD</strong>を使用することで、<strong>Kubernetesネイティブな調整ループ</strong>を確立しました。このオペレーターは、ユーザーが定義した望ましい状態を継続的に監視し、ワークフローインフラに必要な更新を自動的に管理します。これにより、<strong>イベント駆動型</strong>のシームレスな操作が保証され、プラットフォームのロジックが裏側ですべての複雑さを処理します。</p><ul><li><p><strong>GitHubイベントゲートウェイを設計する理由</strong></p></li></ul><p>プラットフォームの応答性を高めるために、<strong>イベントドリブンアーキテクチャー（EDA）</strong>の採用が不可欠でした。CronWorkflowsは信頼性の高いベースラインスケジュールを提供しましたが、ユーザーがダッシュボードから手動でスキャンをトリガーするなど<strong>アドホック実行</strong>を処理できる俊敏性が必要でした。これを達成するために、ペイロードの整合性を検証し、リクエストをインテリジェントにルーティングするための専用の<strong>インジェストゲートウェイ</strong>が必要でした。</p><p>既存のソリューション、特にArgoのネイティブGitHub EventSourceを評価しましたが、 <strong>運用上のオーバーヘッド</strong>や厳格な <strong>GitHub APIクォータ</strong>（例：リポジトリごとのwebhook制限）に関する重大なリスクを特定しました。結果として、これらの制限からインフラを切り離すためにカスタムゲートウェイを構築しました。</p><p>重要なのは、このゲートウェイが移行中に戦略的な<strong>トラフィック制御ポイント</strong>として機能したことです。これはスイッチとして機能し、レガシーシステムから新しいインフラストラクチャへの<strong>段階的かつ詳細なロールアウト</strong>（トラフィックの移行）を実行できるようになりました。これにより、数千のリポジトリのオンボーディングが「ビッグバン」的な切り替えではなく、制御されたリスクのないプロセスになることが保証されました。</p><p></p><h2><strong>教訓</strong></h2><p>私たちが学んだいくつかの教訓は <a href="https://www.elastic.co/about/our-source-code">Elasticソースコード</a>と密接に関連しています。</p><ol><li><p><strong>顧客第一：</strong>プラットフォームはユーザーのために構築されます。したがって、ユーザーのニーズを最優先にすることが重要です。これにより、プラットフォームは、ユーザーとの摩擦を減らし、プラットフォームの拡張を簡素化し、導入を容易にする、効率的に設計されたインフラストラクチャとアプリケーションに形作られます。</p></li><li><p><strong>空間と時間：</strong>時には、最も抵抗の少ない道が<strong>不安定な状況</strong>につながることがあります。当初、既存の順次処理モデルを最適化しようとしましたが、問題は解決されず、むしろ複雑さが増し、未解決の問題が増えただけでした。プラットフォームを並列処理で<strong>再構築</strong>するという大胆な決定には、多大な事前開発が必要でしたが、最終的には持続可能なプラットフォームの成長への道を切り開き、日々の面倒な管理業務を事実上排除しました。</p></li><li><p><strong>ITと依存関係：</strong>プラットフォームは単独では動作できません。その成功は、より広範なエコシステムとどれだけうまく統合できるかによって決まります。当社の場合、シームレスなサービスオンボーディングのための信頼できる情報源となるため、<strong>Backstage</strong>との統合は極めて重要でした。同様に、<strong>Artifactory</strong>に接続することで、プライベートパッケージの更新を効率的に管理できるようになります。他にも重要な統合は多々あります。</p></li><li><p><strong>進歩、シンプルな完璧さ：</strong>実装全体を通じて、当初の想定を継続的にプレッシャーテストし、新たな障壁が出現するたびにそれに適応しました。完璧主義に陥るのではなく、<strong>反復的なアプローチ</strong>を採用し、課題に一つずつ取り組み、実際の状況に合わせて移行戦略を調整しました。</p></li></ol><h2><strong>次のステップ</strong></h2><p>このプラットフォームの提供により、プラットフォームのUXと効率性の向上に役立つ、より有意義な作業が可能になります。いくつかの例を以下に示します。
</p><ul><li><p><strong>自動マージの採用を増やし、ガードレールを設定</strong></p></li></ul><p>自動マージ機能により、面倒な手動タスクが排除され、チームの速度が大幅に向上します。しかし、この速度の向上が安全性を犠牲にすることのないよう、厳格な<strong>ガードレールを</strong>確実に設置する必要があります。
</p><ul><li><p><strong>エンドユーザーエクスペリエンスに関するオブザーバビリティの向上</strong></p></li></ul><p>私たちのロードマップにおける重要な優先事項は、プラットフォームレベルだけでなく、特に<strong>エンドユーザー</strong>の視点からオブザーバビリティを高めることです。インフラの指標を捉えるのは簡単ですが、実際のユーザー体験を理解するにはより深い洞察が必要です。コアユーザー中心の重要業績評価指標（KPI）を定義し、テレメトリが<strong>エスカレート</strong>する前の摩擦点やパフォーマンスの問題を検出できるように取り組んでいます。</p><ul><li><p><strong>より広範な採用を促進するための障壁の除去</strong></p></li></ul><p>将来を見据えた当社の優先事項は、プラットフォームの採用を妨げている障壁を特定して取り除くことです。新しい統合の開発や特定の機能セットの展開が必要な場合でも、当社はデータ主導の計画に取り組んでいます。当社は拡張性を重視したプラットフォームの構築に成功しました。今後は、<strong>その潜在能力を最大限に引き出す</strong>ことに注力していきます。
</p><h2><strong>全体像</strong></h2><p>依存関係管理ワークフロープロジェクトは、より広範な原則を示しています。つまり、<strong>デフォルトの導入モデルを超えてオープンソースツールをスケールする必要がある場合、Kubernetesネイティブパターンが前進する道を提供する</strong>ということです。</p><p>以下を取り入れることで、</p><ul><li><p>構成用のCRD</p></li><li><p>ライフサイクル管理のためのオペレーター</p></li><li><p>応答性を高めるイベント駆動型アーキテクチャ</p></li><li><p>導入用のGitOps</p></li></ul><p>管理するリポジトリの数に関係なく拡張できるオーケストレーションを構築しました。1つのリポジトリをスキャンするパフォーマンスは、管理するリポジトリが100個でも1,000個でも同じです。</p><p>重要なCVEが発表されても、数時間ではなく数分で回答が得られます。それがボトルネックと競争優位の違いです。</p><h2><strong>謝辞</strong></h2><p>このプラットフォームは優れたオープンソースツールを基盤としています。</p><ul><li><p><strong>Kubebuilder：</strong>ワークフローをブートストラップして管理するKubernetes Operatorを起動するために使用したオープンソースフレームワーク。[<a href="https://github.com/kubernetes-sigs/kubebuilder">1</a>][<a href="https://book.kubebuilder.io/">2</a>]</p></li><li><p><strong>Backstage：</strong>Service Catalogを構築し、信頼できる情報源として使用するオープンソースフレームワーク。[<a href="https://github.com/backstage/backstage">1</a>][<a href="https://backstage.io/">2</a>]</p></li><li><p><strong>Argo WorkflowsとArgo Events：</strong>複雑なプロセスを調整し、イベントに基づいて動的な処理を追加するために使用したオープンソーススイート。[1][<a href="https://argo-workflows.readthedocs.io/en/stable/">2</a>][<a href="https://argoproj.github.io/argo-events/">3</a>][<a href="https://github.com/argoproj/argo-events">4</a>]</p></li><li><p><strong>Renovate CLI：</strong>リポジトリを処理するオープンソースの依存関係管理ツール。[<a href="https://github.com/renovatebot/renovate">1</a>][<a href="https://docs.renovatebot.com/getting-started/running/">2</a>]</p></li></ul><p>* AWS Fargateの料金モデルを単一ポッドのコストの基準として使用しました。ただし、当社のワークロードは必ずしもAWSで実行されているわけではなく、本格的なKubernetesクラスターで実行されています。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/dependency-management-kubernetes</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/dependency-management-kubernetes</guid>
    <category><![CDATA[開発者エクスペリエンス]]></category>
    <dc:creator><![CDATA[Nikos Fotiou]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6033995d6660d149/6a170eb5839dfa63f1dcff9b/00519840e6eec7101c1fb096afcae976ee0c454e-1280x720.png" length="0" type="image/png"/>
    <pubDate>Thu, 19 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>