TeamCity

CI/CDTeamCityJetBrainsエンタープライズKotlin DSLIntelliJ商用オンプレミス

CI/CDツール

TeamCity

概要

TeamCityはJetBrainsの商用CI/CDサーバーです。強力なビルド管理、IntelliJ統合、企業向け機能が充実し、Kotlin DSLによる設定とDocker/Kubernetes対応で現代的なDevOpsを実現します。

詳細

TeamCity(チームシティ)は、JetBrains社が開発する商用CI/CD自動化サーバーで、2006年の初回リリース以来、エンタープライズ環境での採用を拡大しています。2025年にはTeamCity 2025.03がリリースされ、TeamCity/Pipelinesの統合による大幅なUI更新、Docker/Podmanサポートの強化、Perforce統合の向上などが実現されました。他のCI/CDツールがYAMLを使用する中、TeamCityはKotlin DSLを採用し、強力な型安全性と優れた開発者体験を提供します。IntelliJ IDEA、Rider、Visual Studioとの緊密な統合により、開発者はIDEから直接CI/CDシステムとの相互作用が可能です。インテリジェントビルド機能により、リポジトリの変更を検出して適切なビルドステップを自動的にトリガーし、インクリメンタルビルド、依存関係ベースのキャッシュ、テスト履歴追跡をサポートします。Kubernetesサポートにより、ワークロードに基づいてビルドエージェントを動的に起動でき、RESTful APIを通じてほぼ全ての機能にアクセス可能です。無料版は100ビルド設定と3エージェントまで利用でき、エンタープライズ機能をすべて含んでいます。

メリット・デメリット

メリット

  • Kotlin DSL設定: YAML不要の型安全な設定記述
  • 強力なIDE統合: IntelliJ、Rider、Visual Studio統合
  • インテリジェントビルド: 自動変更検出とビルドトリガー
  • エンタープライズ機能: 高度なセキュリティとガバナンス
  • Docker/Kubernetes対応: ネイティブコンテナサポート
  • 豊富なVCS統合: Git、Perforce、Mercurial等対応
  • 詳細なレポート: 包括的なビルド分析とメトリクス
  • 無料版の充実: 100設定まで全機能利用可能

デメリット

  • ライセンス費用: 大規模環境での高いコスト
  • 学習コスト: Kotlin DSLと独自概念の習得
  • JetBrains生態系依存: 他社IDE環境での制約
  • サーバー管理: オンプレミス運用の管理負荷
  • リソース消費: メモリとCPU使用量の多さ
  • クラウド制限: フルマネージドサービスの選択肢限定
  • コミュニティ: オープンソース競合との比較でコミュニティ規模

主要リンク

書き方の例

Kotlin DSL基本設定

// .teamcity/settings.kts
version = "2024.03"

project {
    buildType(Build)
    buildType(Test)
    buildType(Deploy)
}

object Build : BuildType({
    name = "Build"
    
    vcs {
        root(DslContext.settingsRoot)
    }
    
    steps {
        script {
            name = "Install Dependencies"
            scriptContent = """
                npm ci
                npm run build
            """.trimIndent()
        }
    }
    
    triggers {
        vcs {
            branchFilter = "+:*"
        }
    }
    
    features {
        dockerSupport {
            loginToRegistry = on {
                dockerRegistryId = "myregistry"
            }
        }
    }
})

object Test : BuildType({
    name = "Test"
    
    dependencies {
        snapshot(Build) {
            onDependencyFailure = FailureAction.FAIL_TO_START
        }
    }
    
    steps {
        script {
            name = "Run Tests"
            scriptContent = """
                npm test
                npm run test:integration
            """.trimIndent()
        }
    }
    
    features {
        coverage {
            tool = jacoco
            rules {
                rule {
                    minValue = 80
                    metric = LINES
                }
            }
        }
    }
})

Docker統合とマルチプラットフォーム

// .teamcity/settings.kts
object DockerBuild : BuildType({
    name = "Docker Build"
    
    params {
        param("docker.image.name", "myapp")
        param("docker.registry", "myregistry.azurecr.io")
    }
    
    steps {
        dockerCommand {
            name = "Build Docker Image"
            commandType = build {
                source = file {
                    path = "Dockerfile"
                }
                namesAndTags = "%docker.registry%/%docker.image.name%:%build.number%"
                commandArgs = "--platform linux/amd64,linux/arm64"
            }
        }
        
        dockerCommand {
            name = "Push Docker Image"
            commandType = push {
                namesAndTags = "%docker.registry%/%docker.image.name%:%build.number%"
            }
        }
    }
    
    features {
        dockerSupport {
            loginToRegistry = on {
                dockerRegistryId = "azure-registry"
            }
        }
        
        buildCache {
            use {
                rules = """
                    +:**/node_modules
                    +:**/dist
                """.trimIndent()
            }
            publish {
                rules = """
                    +:**/node_modules
                    +:**/dist
                """.trimIndent()
            }
        }
    }
})

Kubernetes デプロイメント

// .teamcity/settings.kts
object KubernetesDeploy : BuildType({
    name = "Deploy to Kubernetes"
    
    params {
        param("k8s.namespace", "production")
        param("k8s.cluster", "production-cluster")
        param("app.image", "%docker.registry%/%docker.image.name%:%build.number%")
    }
    
    dependencies {
        snapshot(DockerBuild) {
            onDependencyFailure = FailureAction.FAIL_TO_START
        }
    }
    
    steps {
        script {
            name = "Update Kubernetes Manifests"
            scriptContent = """
                sed -i 's|IMAGE_TAG|%app.image%|g' k8s/deployment.yaml
                kubectl apply -f k8s/ --namespace=%k8s.namespace%
                kubectl rollout status deployment/myapp --namespace=%k8s.namespace%
            """.trimIndent()
        }
        
        script {
            name = "Health Check"
            scriptContent = """
                kubectl get pods --namespace=%k8s.namespace%
                kubectl logs deployment/myapp --namespace=%k8s.namespace% --tail=50
            """.trimIndent()
        }
    }
    
    features {
        kubernetes {
            connection = kubernetesConnection {
                name = "k8s-connection"
                apiServerUrl = "https://k8s-api.example.com"
                namespace = "%k8s.namespace%"
                authStrategy = token {
                    token = "%secure:k8s.token%"
                }
            }
        }
        
        notifications {
            notifier = slackNotifier {
                connection = slackConnection {
                    id = "slack-connection"
                    botToken = "%secure:slack.token%"
                }
                messageFormat = teamcityFormat()
                channel = "#deployments"
            }
        }
    }
})

複雑なワークフローとテンプレート

// .teamcity/settings.kts
object PipelineTemplate : Template({
    name = "Standard Pipeline"
    
    params {
        param("app.name", "")
        param("git.branch", "main")
        param("environment", "staging")
    }
    
    vcs {
        root(DslContext.settingsRoot)
    }
    
    steps {
        script {
            name = "Setup Environment"
            scriptContent = """
                echo "Setting up %app.name% for %environment%"
                npm ci
            """.trimIndent()
        }
        
        script {
            name = "Run Quality Checks"
            scriptContent = """
                npm run lint
                npm run type-check
                npm audit
            """.trimIndent()
        }
        
        script {
            name = "Build Application"
            scriptContent = """
                npm run build
                tar -czf %app.name%-%build.number%.tar.gz dist/
            """.trimIndent()
        }
        
        script {
            name = "Run Tests"
            scriptContent = """
                npm test -- --coverage
                npm run test:e2e
            """.trimIndent()
        }
    }
    
    triggers {
        vcs {
            branchFilter = "+:%git.branch%"
        }
    }
    
    features {
        pullRequests {
            vcsRootExtId = "${DslContext.settingsRoot.id}"
            provider = github {
                authType = token {
                    token = "%secure:github.token%"
                }
                filterAuthorRole = PullRequests.GitHubRoleFilter.MEMBER
            }
        }
    }
})

// テンプレート使用例
object WebAppPipeline : BuildType({
    templates(PipelineTemplate)
    name = "Web App Pipeline"
    
    params {
        param("app.name", "webapp")
        param("environment", "production")
    }
})

object APIPipeline : BuildType({
    templates(PipelineTemplate)
    name = "API Pipeline"
    
    params {
        param("app.name", "api")
        param("git.branch", "develop")
    }
})

セキュリティとエンタープライズ機能

// .teamcity/settings.kts
object SecurePipeline : BuildType({
    name = "Production Deployment"
    
    params {
        param("env.VAULT_ADDR", "https://vault.example.com")
        password("vault.token", "%secure:vault.production.token%")
    }
    
    steps {
        script {
            name = "Security Scan"
            scriptContent = """
                # SonarQube analysis
                sonar-scanner \
                  -Dsonar.projectKey=%teamcity.project.id% \
                  -Dsonar.sources=src/ \
                  -Dsonar.host.url=%secure:sonar.url% \
                  -Dsonar.login=%secure:sonar.token%
                
                # Dependency vulnerability scan
                npm audit --audit-level high
                
                # SAST scan
                semgrep --config=auto src/
            """.trimIndent()
        }
        
        script {
            name = "Retrieve Secrets"
            scriptContent = """
                export VAULT_TOKEN=%vault.token%
                DATABASE_URL=$(vault kv get -field=url secret/app/database)
                API_KEY=$(vault kv get -field=key secret/app/api)
                
                echo "##teamcity[setParameter name='env.DATABASE_URL' value='$DATABASE_URL']"
                echo "##teamcity[setParameter name='env.API_KEY' value='$API_KEY']"
            """.trimIndent()
        }
    }
    
    features {
        approvals {
            approval {
                approvalRules = "user:admin,user:manager"
                timeout = 3600
            }
        }
        
        investigations {
            defaultAssignee = user("teamlead")
            defaultResponsible = user("devops")
        }
    }
    
    requirements {
        contains("teamcity.agent.os.name", "Linux")
        moreThan("teamcity.agent.hardware.memorySizeMb", "4096")
    }
})