GitLab CI/CD

CI/CDGitLabYAML自動化DevOpsパイプラインコンテナセキュリティ

CI/CDツール

GitLab CI/CD

概要

GitLab CI/CDは、GitLabに統合されたCI/CDシステムです。強力なパイプライン機能、Auto DevOps、Kubernetes統合、セキュリティスキャンが特徴で、包括的なDevOpsプラットフォームを提供します。

詳細

GitLab CI/CD(ギットラブ シーアイ・シーディー)は、GitLab社が提供する統合型CI/CD(継続的インテグレーション・継続的デリバリー)プラットフォームです。2011年のGitLab発足以来進化を続け、現在では単なるコード管理を超えた包括的なDevOpsソリューションを提供しています。リポジトリ内の.gitlab-ci.ymlファイルでパイプラインを定義し、ビルド、テスト、デプロイを自動化します。GitLab Runnerによる多様な実行環境サポート(Docker、Kubernetes、VM)、マトリックスビルド、並列実行により高速で柔軟なCI/CDを実現します。2024年にはCI/CDカタログが正式リリースされ、再利用可能なコンポーネントを通じて効率的なパイプライン構築が可能になりました。Auto DevOps機能により設定なしでCI/CDパイプラインを自動生成し、SAST、DAST、dependency scanningなどの包括的なセキュリティスキャンを統合しています。GitLab独自のカンバンボード、Issue管理、Merge Request、Container Registry、Package Registryとの緊密な統合により、開発からデプロイまでのワンストップソリューションを提供します。

メリット・デメリット

メリット

  • オールインワンプラットフォーム: Git、CI/CD、セキュリティ、監視を統合
  • Auto DevOps: ゼロ設定でCI/CDパイプラインを自動生成
  • 強力なセキュリティ機能: SAST、DAST、dependency scanning内蔵
  • 柔軟な実行環境: Docker、Kubernetes、VM、セルフホストランナー対応
  • CI/CDカタログ: 再利用可能なコンポーネントでパイプライン効率化
  • 包括的な統合: Issue、MR、Container Registryとの緊密連携
  • GitOps対応: Kubernetes環境でのGitOpsワークフローサポート
  • 無料利用枠: GitLab.comで毎月400分の無料CI/CD実行時間

デメリット

  • 学習コスト: YAML記法と複雑な機能セットの理解が必要
  • 実行時間制限: GitLab.comではジョブあたり最大8時間の制限
  • 料金: セルフホストでも大規模利用時は有料機能が必要
  • リソース消費: 機能豊富な分、システムリソースを多く消費
  • デバッグの複雑さ: パイプライン失敗時の原因特定が困難
  • ベンダーロックイン: GitLab固有機能への依存リスク
  • パフォーマンス: 大規模プロジェクトでのUIレスポンス低下

主要リンク

書き方の例

基本的なパイプライン設定

# .gitlab-ci.yml
stages:
  - build
  - test
  - deploy

default:
  image: ubuntu:latest

variables:
  DOCKER_DRIVER: overlay2

build_job:
  stage: build
  script:
    - echo "Building the application..."
    - npm install
    - npm run build
  artifacts:
    paths:
      - build/
    expire_in: 1 hour

test_job:
  stage: test
  script:
    - echo "Running tests..."
    - npm run test
  dependencies:
    - build_job

deploy_job:
  stage: deploy
  script:
    - echo "Deploying to production..."
    - ./deploy.sh
  environment:
    name: production
    url: https://example.com
  only:
    - main

Node.jsプロジェクトのCI/CD

# .gitlab-ci.yml
image: node:18

stages:
  - install
  - test
  - build
  - deploy

cache:
  paths:
    - node_modules/
    - .npm/

variables:
  npm_config_cache: "$CI_PROJECT_DIR/.npm"

install_dependencies:
  stage: install
  script:
    - npm ci --cache .npm --prefer-offline
  artifacts:
    paths:
      - node_modules/
    expire_in: 30 minutes

lint:
  stage: test
  script:
    - npm run lint
  dependencies:
    - install_dependencies

unit_tests:
  stage: test
  script:
    - npm run test:unit
  coverage: '/Lines\s*:\s*(\d+\.\d+)%/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml
    paths:
      - coverage/
  dependencies:
    - install_dependencies

build_app:
  stage: build
  script:
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 week
  dependencies:
    - install_dependencies
  only:
    - main
    - develop

deploy_staging:
  stage: deploy
  script:
    - echo "Deploying to staging..."
    - npm run deploy:staging
  environment:
    name: staging
    url: https://staging.example.com
  dependencies:
    - build_app
  only:
    - develop

deploy_production:
  stage: deploy
  script:
    - echo "Deploying to production..."
    - npm run deploy:production
  environment:
    name: production
    url: https://example.com
  dependencies:
    - build_app
  only:
    - main
  when: manual

Docker イメージビルドとレジストリプッシュ

# .gitlab-ci.yml
stages:
  - build
  - test
  - push

variables:
  DOCKER_HOST: tcp://docker:2376
  DOCKER_TLS_CERTDIR: "/certs"
  DOCKER_TLS_VERIFY: 1
  DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"

services:
  - docker:dind

before_script:
  - docker info

build_image:
  stage: build
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker save $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA > image.tar
  artifacts:
    paths:
      - image.tar
    expire_in: 1 hour

test_image:
  stage: test
  script:
    - docker load < image.tar
    - docker run --rm $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA npm test
  dependencies:
    - build_image

push_image:
  stage: push
  script:
    - docker load < image.tar
    - echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
    - docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - docker push $CI_REGISTRY_IMAGE:latest
  dependencies:
    - build_image
  only:
    - main

マトリックスビルドとマルチプラットフォーム対応

# .gitlab-ci.yml
stages:
  - test
  - build

test_matrix:
  stage: test
  image: node:${NODE_VERSION}
  script:
    - npm install
    - npm test
  parallel:
    matrix:
      - NODE_VERSION: ["16", "18", "20", "22"]
        PLATFORM: ["linux", "darwin"]
  except:
    variables:
      - $NODE_VERSION == "16" && $PLATFORM == "darwin"

build_multiarch:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  before_script:
    - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
    - docker buildx create --use --name multiarch-builder
  script:
    - echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
    - docker buildx build 
      --platform linux/amd64,linux/arm64,linux/arm/v7 
      --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG 
      --tag $CI_REGISTRY_IMAGE:latest 
      --push .
  only:
    - tags

条件付きパイプラインとマニュアルデプロイ

# .gitlab-ci.yml
workflow:
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_TAG
    - if: $CI_PIPELINE_SOURCE == "schedule"

stages:
  - test
  - security
  - deploy

variables:
  ENVIRONMENT: "development"

.base_job: &base_job
  before_script:
    - echo "Starting job..."
  after_script:
    - echo "Job completed."

unit_tests:
  <<: *base_job
  stage: test
  script:
    - npm run test
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"
  coverage: '/Lines\s*:\s*(\d+\.\d+)%/'

security_scan:
  stage: security
  script:
    - echo "Running security scans..."
  include:
    - template: Security/SAST.gitlab-ci.yml
    - template: Security/Dependency-Scanning.gitlab-ci.yml
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
    - if: $CI_PIPELINE_SOURCE == "schedule"

deploy_staging:
  <<: *base_job
  stage: deploy
  script:
    - echo "Deploying to staging environment..."
    - ./deploy.sh staging
  environment:
    name: staging/$CI_COMMIT_REF_SLUG
    url: https://staging-$CI_COMMIT_REF_SLUG.example.com
    on_stop: stop_staging
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      when: manual

stop_staging:
  <<: *base_job
  stage: deploy
  script:
    - echo "Stopping staging environment..."
    - ./cleanup.sh staging
  environment:
    name: staging/$CI_COMMIT_REF_SLUG
    action: stop
  when: manual
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      when: manual

deploy_production:
  <<: *base_job
  stage: deploy
  script:
    - echo "Deploying to production..."
    - ./deploy.sh production
  environment:
    name: production
    url: https://example.com
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual
    - if: $CI_COMMIT_TAG
      when: manual

セキュリティスキャンとコンプライアンス

# .gitlab-ci.yml
include:
  - template: Security/SAST.gitlab-ci.yml
  - template: Security/Dependency-Scanning.gitlab-ci.yml
  - template: Security/Container-Scanning.gitlab-ci.yml
  - template: Security/DAST.gitlab-ci.yml

stages:
  - build
  - test
  - security
  - deploy

variables:
  SAST_EXCLUDED_PATHS: "tests/, spec/, vendor/"
  SECURE_LOG_LEVEL: "debug"

build_app:
  stage: build
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

container_scanning:
  variables:
    CS_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  dependencies:
    - build_app

dast_scan:
  stage: security
  variables:
    DAST_WEBSITE: https://staging.example.com
    DAST_AUTH_URL: https://staging.example.com/login
  script:
    - echo "Running DAST scan..."
  artifacts:
    reports:
      dast: gl-dast-report.json
  only:
    - schedules

security_report:
  stage: security
  script:
    - echo "Generating security compliance report..."
    - ./generate-security-report.sh
  artifacts:
    reports:
      junit: security-report.xml
    paths:
      - security-report.html
  dependencies:
    - sast
    - dependency_scanning
    - container_scanning

Kubernetes デプロイメント

# .gitlab-ci.yml
stages:
  - build
  - deploy

variables:
  KUBE_NAMESPACE: $CI_PROJECT_NAME-$CI_ENVIRONMENT_SLUG
  ROLLOUT_RESOURCE_TYPE: deployment

build:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

.deploy_template: &deploy_template
  image: bitnami/kubectl:latest
  before_script:
    - kubectl config use-context $KUBE_CONTEXT
    - kubectl create namespace $KUBE_NAMESPACE --dry-run=client -o yaml | kubectl apply -f -
    - envsubst < k8s/deployment.yaml | kubectl apply -f -
    - envsubst < k8s/service.yaml | kubectl apply -f -
  script:
    - kubectl rollout status $ROLLOUT_RESOURCE_TYPE/$CI_PROJECT_NAME -n $KUBE_NAMESPACE
    - kubectl get all -n $KUBE_NAMESPACE

deploy_staging:
  <<: *deploy_template
  stage: deploy
  variables:
    KUBE_CONTEXT: staging-cluster
    REPLICAS: "2"
  environment:
    name: staging
    url: https://staging.example.com
    kubernetes:
      namespace: $KUBE_NAMESPACE
  only:
    - develop

deploy_production:
  <<: *deploy_template
  stage: deploy
  variables:
    KUBE_CONTEXT: production-cluster
    REPLICAS: "5"
  environment:
    name: production
    url: https://example.com
    kubernetes:
      namespace: $KUBE_NAMESPACE
  only:
    - main
  when: manual