TeamCity On-Premises 2026.2 Help

Kubernetes Operator: Kubernetes クラスターに TeamCity サーバーをデプロイする

Kubernetes (英語) は、ダウンタイムなしで更新を行い、安定性、信頼性、拡張性に優れたアプリケーションをデプロイするのに役立つさまざまな機能とツールを提供します。

手動デプロイ

Kubernetes クラスターにサーバーをデプロイする場合、次の TeamCity 機能が最終的なセットアップと全体的なアプローチに影響する可能性があります。

マルチノード設定

継続的に実行されるプロセスと同様に、pods と個々のコンテナーは、いずれ障害が発生したり、強制的に削除されたりする可能性があります。 アプリケーションが単一の pod で実行されている場合、Kubernetes 管理者が障害が発生した pod を適切なタイミングで特定し、手動で交換できなかった場合、これらのインシデントによってワークフロー全体が失敗する可能性があります。

この脅威を軽減し、pod の自己修復を促進し、最終的に K8S ワークフローの整合性を確保するため、Kubernetes はレプリカをサポートしています。レプリカとは、単一または複数のノードで同時に実行される、同じ pod の同一インスタンスまたはコピーです。 pod に障害が発生すると、利用可能なレプリカが代わりを担い、Kubernetes は障害が発生した pod を再起動して、必要なレプリカ数を復元します。

TeamCity は、サーバーの完全に同一のインスタンスを同時に実行することをサポートしていません。 複数のインスタンスが存在する場合、TeamCity はそれらを マルチノード設定の一部として扱い、メインノードまたはセカンダリノードとしてランク付けするよう求めます。 つまり、異なる TEAMCITY_SERVER_OPTS 環境変数値を持つ N 個のデプロイを作成して、正解の ノード責任を TeamCity ノードに割り当てる必要があります。

共有リソース

実稼働環境では、すべての TeamCity ノードが以下にアクセスできることを確認する必要があります:

データディレクトリは、複数の仮想マシン (K8S ノード) で使用できるように、NFS/SMB ボリュームである必要があります。

例 1: テストセットアップ

次のマニフェストは、単一の TeamCity ノードと外部データベースを使用したシンプルなテストセットアップを示しています。

apiVersion: apps/v1 kind: Deployment metadata: name: teamcity-server spec: replicas: 1 #always 1 selector: matchLabels: app: teamcity-server template: metadata: labels: app: teamcity-server spec: containers: - name: teamcity-server image: jetbrains/teamcity-server:latest env: - name: TEAMCITY_SERVER_OPTS value: -Dteamcity.server.rootURL=http://$(POD_NAME).$(POD_NAMESPACE) - name: TEAMCITY_DATA_PATH value: /data/teamcity_server/datadir - name: POD_NAME valueFrom: fieldRef: apiVersion: v1 fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: apiVersion: v1 fieldPath: metadata.namespace ports: - containerPort: 8111 volumeMounts: - name: teamcity-data mountPath: /data/teamcity_server/datadir volumes: - name: teamcity-data emptyDir: {}

例 2: 一台の仮想マシン上の二つの TeamCity レプリカ

以下のサンプルは、2 つのレプリカと外部データベースを使用した、より安定したソリューションを実装しています。 ただし、セットアップを単純化するため、どちらも同じ仮想マシン上で実行され、ローカルディレクトリを TeamCity データディレクトリとして使用しています。

apiVersion: v1 kind: Service metadata: name: mysql spec: ports: - port: 3306 selector: app: mysql clusterIP: None --- apiVersion: v1 kind: ConfigMap metadata: name: mysql-initdb-config data: init.sql: | CREATE DATABASE IF NOT EXISTS teamcity; --- apiVersion: apps/v1 kind: Deployment metadata: name: mysql spec: selector: matchLabels: app: mysql strategy: type: Recreate template: metadata: labels: app: mysql spec: volumes: - name: mysql-initdb configMap: name: mysql-initdb-config containers: - image: mysql:8 name: mysql env: - name: MYSQL_ROOT_PASSWORD value: password ports: - containerPort: 3306 name: mysql volumeMounts: - name: mysql-initdb mountPath: /docker-entrypoint-initdb.d --- #based on database.properties apiVersion: v1 data: connectionProperties.password: cGFzc3dvcmQ= connectionProperties.user: cm9vdA== connectionUrl: amRiYzpteXNxbDovL215c3FsLmRlZmF1bHQ6MzMwNi90ZWFtY2l0eQ== kind: Secret metadata: name: database-properties --- apiVersion: apps/v1 kind: Deployment metadata: name: teamcity-server spec: replicas: 2 selector: matchLabels: app: teamcity-server template: metadata: labels: app: teamcity-server spec: containers: - name: teamcity-server image: jetbrains/teamcity-server:latest env: - name: TEAMCITY_DATA_PATH value: /data/teamcity_server/datadir - name: TEAMCITY_DB_USER valueFrom: secretKeyRef: key: connectionProperties.user name: database-properties - name: TEAMCITY_DB_PASSWORD valueFrom: secretKeyRef: key: connectionProperties.password name: database-properties - name: TEAMCITY_DB_URL valueFrom: secretKeyRef: key: connectionUrl name: database-properties ports: - containerPort: 8111 volumeMounts: - name: teamcity-data mountPath: /data/teamcity_server/datadir volumes: - name: teamcity-data persistentVolumeClaim: claimName: teamcity-node-volume --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: teamcity-node-volume spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: standard

例 3: 責任が異なる複数のノード

以下のサンプルでは、メインノードとセカンダリノードそれぞれに、2 つの独立したデプロイレイヤーを設定しています。 各デプロイの 環境 セクションでは、 TEAMCITY_SERVER_OPTS 環境変数を設定して、各ノードに異なるロールを割り当てています。

#database deployment apiVersion: v1 kind: Service metadata: name: mysql spec: ports: - port: 3306 selector: app: mysql clusterIP: None --- apiVersion: v1 kind: ConfigMap metadata: name: mysql-initdb-config data: init.sql: | CREATE DATABASE IF NOT EXISTS teamcity; --- apiVersion: apps/v1 kind: Deployment metadata: name: mysql spec: selector: matchLabels: app: mysql strategy: type: Recreate template: metadata: labels: app: mysql spec: volumes: - name: mysql-initdb configMap: name: mysql-initdb-config containers: - image: mysql:8 name: mysql env: - name: MYSQL_ROOT_PASSWORD value: password ports: - containerPort: 3306 name: mysql volumeMounts: - name: mysql-initdb mountPath: /docker-entrypoint-initdb.d --- apiVersion: v1 data: connectionProperties.password: qwerty connectionProperties.user: johndoe connectionUrl: foobar kind: Secret metadata: name: database-properties --- apiVersion: apps/v1 kind: Deployment #Deployment #1, main TeamCity node metadata: name: teamcity-server-main spec: replicas: 1 #always 1 selector: matchLabels: app: teamcity-server template: metadata: labels: app: teamcity-server spec: containers: - name: teamcity-server image: jetbrains/teamcity-server:latest env: - name: TEAMCITY_SERVER_OPTS value: -Dteamcity.server.nodeId=main-node -Dteamcity.server.rootURL=http://$(POD_NAME).$(POD_NAMESPACE) -Dteamcity.server.responsibilities=MAIN_NODE,CAN_PROCESS_BUILD_MESSAGES,CAN_CHECK_FOR_CHANGES,CAN_PROCESS_BUILD_TRIGGERS,CAN_PROCESS_USER_DATA_MODIFICATION_REQUESTS - name: TEAMCITY_DATA_PATH value: /data/teamcity_server/datadir - name: TEAMCITY_DB_USER valueFrom: secretKeyRef: key: connectionProperties.user name: database-properties - name: TEAMCITY_DB_PASSWORD valueFrom: secretKeyRef: key: connectionProperties.password name: database-properties - name: TEAMCITY_DB_URL valueFrom: secretKeyRef: key: connectionUrl name: database-properties - name: POD_NAME valueFrom: fieldRef: apiVersion: v1 fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: apiVersion: v1 fieldPath: metadata.namespace ports: - containerPort: 8111 volumeMounts: - name: teamcity-data mountPath: /data/teamcity_server/datadir volumes: - name: teamcity-data persistentVolumeClaim: claimName: teamcity-node-volume --- apiVersion: apps/v1 kind: Deployment #Deployment #2, secondary TeamCity node metadata: name: teamcity-server-additional-node-0 spec: replicas: 1 #always 1 selector: matchLabels: app: teamcity-server template: metadata: labels: app: teamcity-server spec: containers: - name: teamcity-server image: jetbrains/teamcity-server:latest env: - name: TEAMCITY_SERVER_OPTS value: -Dteamcity.server.nodeId=secondary-node-0 -Dteamcity.server.rootURL=http://$(POD_NAME).$(POD_NAMESPACE) -Dteamcity.server.responsibilities=CAN_PROCESS_BUILD_MESSAGES,CAN_CHECK_FOR_CHANGES,CAN_PROCESS_BUILD_TRIGGERS,CAN_PROCESS_USER_DATA_MODIFICATION_REQUESTS - name: TEAMCITY_DATA_PATH value: /data/teamcity_server/datadir - name: TEAMCITY_DB_USER valueFrom: secretKeyRef: key: connectionProperties.user name: database-properties - name: TEAMCITY_DB_PASSWORD valueFrom: secretKeyRef: key: connectionProperties.password name: database-properties - name: TEAMCITY_DB_URL valueFrom: secretKeyRef: key: connectionUrl name: database-properties - name: POD_NAME valueFrom: fieldRef: apiVersion: v1 fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: apiVersion: v1 fieldPath: metadata.namespace ports: - containerPort: 8111 volumeMounts: - name: teamcity-data mountPath: /data/teamcity_server/datadir volumes: - name: teamcity-data persistentVolumeClaim: claimName: teamcity-node-volume --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: teamcity-node-volume spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: standard

Helm 経由で TeamCity サーバーをデプロイする

Helm(英語) は Kubernetes 用のパッケージマネージャーで、YAML マニフェストのテンプレート化、バージョン管理、設定、再利用を可能にします。 その結果、YAML のコピーと貼り付けを避け、複数の環境 (dev、ステージング、prod など) を簡単に管理し、デプロイの更新/ロールバックをより安全に行えます。

これらの利点は、TeamCity をデプロイする場合にも適用されます。 例: 実際のアプリケーションに最適な サンプル 3 は、上記の次の問題を示しています。

  • 重複 - メインとセカンダリの TeamCity デプロイは約 90% 同一です。

  • ハードコードされた値 - イメージタグ、ノード ID、責任、ストレージサイズなどの生の値。

  • 緊密な結合 - DB 構成、シークレット、PVC、アプリデプロイがすべて混在します。

  • スケーラビリティが低い - 追加のセカンダリノードを作成するには、別のデプロイをコピーして貼り付ける必要があります。

これらの問題を解消するには、マニフェストを個別のファイルに分割します。 Helm チャートの構造は次のようになります。

teamcity/ ├── Chart.yaml ├── values.yaml # The majority of your edits will happen here ├── templates/ │ ├── mysql/ # The database template │ │ ├── service.yaml │ │ ├── deployment.yaml │ │ └── configmap.yaml │ ├── teamcity/ # Template for TeamCity nodes │ │ ├── deployment.yaml │ │ └── service.yaml │ ├── secrets.yaml # Sensitive data │ ├── pvc.yaml # The storage template │ └── _helpers.tpl
値.yaml

このファイルは、変数と一意の値の大部分を別のレイヤーにまとめています。 これらの値 (たとえば、TeamCity サーバーのイメージタグ) は、グローバルマニフェストを編集せずに変更できます。

teamcity: image: jetbrains/teamcity-server tag: latest dataPath: /data/teamcity_server/datadir mainNode: enabled: true nodeId: main-node responsibilities: - MAIN_NODE - CAN_PROCESS_BUILD_MESSAGES - CAN_CHECK_FOR_CHANGES - CAN_PROCESS_BUILD_TRIGGERS - CAN_PROCESS_USER_DATA_MODIFICATION_REQUESTS secondaryNodes: - name: secondary-node-0 responsibilities: - CAN_PROCESS_BUILD_MESSAGES - CAN_CHECK_FOR_CHANGES - CAN_PROCESS_BUILD_TRIGGERS - CAN_PROCESS_USER_DATA_MODIFICATION_REQUESTS database: secretName: database-properties

一意でない行でマニフェストを肥大化させることなく、 secondaryNodes セクションの下に新しいエントリを追加するだけで、TeamCity セットアップを簡単にスケールできます…

secondaryNodes: - name: secondary-node-0 responsibilities: ... - name: secondary-node-1 responsibilities: ... - name: secondary-node-2 responsibilities: ...

... そして helm upgrade teamcity ./teamcity コマンドを呼び出します。

テンプレート/teamcity/デプロイ.yaml

values.yml ファイルを配置すると、コアノードのデプロイマニフェストは次のように削減されます。

# --------------------------------------------------------- # Main TeamCity node # --------------------------------------------------------- apiVersion: apps/v1 kind: Deployment metadata: name: teamcity-server-main spec: replicas: 1 selector: matchLabels: app: teamcity-server node: main template: metadata: labels: app: teamcity-server node: main spec: containers: - name: teamcity-server image: {{ .Values.teamcity.image }}:{{ .Values.teamcity.tag }} env: - name: TEAMCITY_SERVER_OPTS value: > -Dteamcity.server.nodeId={{ .Values.teamcity.mainNode.nodeId }} -Dteamcity.server.rootURL=http://$(POD_NAME).$(POD_NAMESPACE) -Dteamcity.server.responsibilities={{ join "," .Values.teamcity.mainNode.responsibilities }} - name: TEAMCITY_DATA_PATH value: {{ .Values.teamcity.dataPath }} - name: TEAMCITY_DB_USER valueFrom: secretKeyRef: name: {{ .Values.database.secretName }} key: connectionProperties.user - name: TEAMCITY_DB_PASSWORD valueFrom: secretKeyRef: name: {{ .Values.database.secretName }} key: connectionProperties.password - name: TEAMCITY_DB_URL valueFrom: secretKeyRef: name: {{ .Values.database.secretName }} key: connectionUrl - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace ports: - containerPort: 8111 volumeMounts: - name: teamcity-data mountPath: {{ .Values.teamcity.dataPath }} volumes: - name: teamcity-data persistentVolumeClaim: claimName: teamcity-node-volume # --------------------------------------------------------- # Secondary TeamCity nodes (rendered from values.yaml) # --------------------------------------------------------- {{- range $index, $node := .Values.teamcity.secondaryNodes }} --- apiVersion: apps/v1 kind: Deployment metadata: name: teamcity-server-{{ $node.name }} spec: replicas: 1 selector: matchLabels: app: teamcity-server node: {{ $node.name }} template: metadata: labels: app: teamcity-server node: {{ $node.name }} spec: containers: - name: teamcity-server image: {{ $.Values.teamcity.image }}:{{ $.Values.teamcity.tag }} env: - name: TEAMCITY_SERVER_OPTS value: > -Dteamcity.server.nodeId={{ $node.name }} -Dteamcity.server.rootURL=http://$(POD_NAME).$(POD_NAMESPACE) -Dteamcity.server.responsibilities={{ join "," $node.responsibilities }} - name: TEAMCITY_DATA_PATH value: {{ $.Values.teamcity.dataPath }} - name: TEAMCITY_DB_USER valueFrom: secretKeyRef: name: {{ $.Values.database.secretName }} key: connectionProperties.user - name: TEAMCITY_DB_PASSWORD valueFrom: secretKeyRef: name: {{ $.Values.database.secretName }} key: connectionProperties.password - name: TEAMCITY_DB_URL valueFrom: secretKeyRef: name: {{ $.Values.database.secretName }} key: connectionUrl - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace ports: - containerPort: 8111 volumeMounts: - name: teamcity-data mountPath: {{ $.Values.teamcity.dataPath }} volumes: - name: teamcity-data persistentVolumeClaim: claimName: teamcity-node-volume {{- end }}

セカンダリ TeamCity ノードの数に関係なく、この単一の定義は変更されません。 Helm は、このファイルを複数の Kubernetes デプロイ (メインノード用にひとつ、さらに 範囲 により values.yml ファイルで宣言されたリストから N 個のセカンダリノードデプロイ) に自動的にレンダリングします。

テンプレート/mysql/デプロイ.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "teamcity.mysqlName" . }} spec: strategy: type: Recreate selector: matchLabels: app: mysql template: metadata: labels: app: mysql spec: containers: - name: mysql image: {{ .Values.mysql.image }} env: - name: MYSQL_ROOT_PASSWORD value: {{ .Values.mysql.rootPassword | quote }} ports: - containerPort: 3306
テンプレート/mysql/configmap.yaml
apiVersion: v1 kind: ConfigMap metadata: name: mysql-initdb-config data: init.sql: | CREATE DATABASE IF NOT EXISTS {{ .Values.mysql.database }};
テンプレート/pvc.yaml
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: teamcity-node-volume spec: accessModes: - ReadWriteOnce resources: requests: storage: {{ .Values.persistence.size }} storageClassName: {{ .Values.persistence.storageClass }}

_helpers.tpl で定義された Go テンプレートを使用して、このセットアップを最適化し続けることができます。 たとえば、共通のコンテナー構成を作成できます ...

{{- define "teamcity.containerBase" -}} image: {{ .Values.teamcity.image }}:{{ .Values.teamcity.tag }} ports: - containerPort: 8111 volumeMounts: - name: teamcity-data mountPath: {{ .Values.teamcity.dataPath }} env: - name: TEAMCITY_DATA_PATH value: {{ .Values.teamcity.dataPath }} - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace {{ include "teamcity.dbEnv" . | indent 2 }} {{- end -}}

... そして TEAMCITY_SERVER_OPTS ビルダー ...

{{- define "teamcity.serverOpts" -}} -Dteamcity.server.nodeId={{ .nodeId }} -Dteamcity.server.rootURL=http://$(POD_NAME).$(POD_NAMESPACE) -Dteamcity.server.responsibilities={{ join "," .responsibilities }} {{- end -}}

... deployment.yaml ファイルをさらにきれいにするには:

containers: - name: teamcity-server {{ include "teamcity.containerBase" . | indent 4 }} env: - name: TEAMCITY_SERVER_OPTS value: > {{ include "teamcity.serverOpts" (dict "nodeId" .Values.teamcity.mainNode.nodeId "responsibilities" .Values.teamcity.mainNode.responsibilities ) }}
2026 年 9 月 11 日