0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Jenkinsパイプライン試行錯誤奮闘記|第2章

Last updated at Posted at 2025-10-09

第2章:Jenkins サーバー構築フェーズ

第1章では「Jenkinsとは?」「なぜ導入するのか?」をお話しました。
ここからは実際に Jenkins サーバーを構築していくフェーズ に入ります!

そもそも、現在のシェルスクリプトを Jenkinsfile に落とし込み実行すれば、リリース自体は簡単にできます。(これまで自分がsshでログインして実行していたものを肩代わりしてもらうだけ)

しかし、それでは学びが少なく、せっかく Jenkins を導入する意味も薄れてしまいます。

そこで、今回は Jenkins 特有の GitHub との連携 や AWS との統合、さらに プラグインの活用 などを実際に取り入れながら、
「なぜこの処理が必要なのか」「どこで何をしているのか」 を理解することを目指しました。

そのために、既存のシェルスクリプトを自分で読み解き、Jenkinsfile へ移行することに挑戦しています。
これにより処理の流れを明確に理解できるだけでなく、Jenkins 上で自動化するうえでのポイント(環境変数の扱い、権限、リリースディレクトリの切替など)も学ぶことができました。
(結果として、自分が行った作業の非効率さもよく理解しました😆が、良い学びであったとbig-Aも言っていたので良かったと💦)
まずは、今回の環境を整理して「どんな前提で構築するのか?」を明確にしておきましょう。

⚠️ 本記事は 自己学習のアウトプット を目的としています。
細かな環境設定や運用上の最適化については割愛していますので、実際に導入される際はご自身の環境に合わせて検討をお願いします。

前提条件

・Jenkinsサーバは踏み台経由でアクセスできるプライベートサブネットに配備
・勉強用のAppサーバは本番のAMIからプライベートサブネット(Jenkinsと同じ)に配備
・Appサーバはpython flaskをバックエンドで利用
→python環境に必用なものはrequirements.txtからpipでインストール
・フロントエンドはnodeを利用しnpmでビルドしている
・.envの内容はAWS Secrets Managerから取得している
・gunicornでhttp接続(App接続)をハンドリングしており、systemd管理としている

今回のJenkinsの学習利用は、稼働しているファンサイトの複製サーバーで実行していく想定であり、今回使用する踏み台(Bastion)は既存踏み台を使用しました。

今回の設計

  • デプロイフロー

1, リリースディレクトリ作成・pipインストール
→ディレクトリ作成:日付付きディレクトリで履歴を保持、失敗時も戻せる
→pipインストール:reqquirements.txtをJenkinsサーバーから直接 App サーバーへファイルをコピーしてからAppで実行

2, Jenkins でビルド・成果物作成
→GitHub からコード・資材を取得し、Jenkins サーバー上に配置

3, SSHで App サーバーに成果物転送
→Jenkinsサーバーから直接 App サーバーへファイルをコピー

4, シンボリックリンク切替(sudo権限必要)
→上書きせずに新しいリリースを反映

5, サービス再起動(sudo権限必要)
→gunicornサービスの再起動

6, 古いリリースを削除(オプション)

  • Jenkinsfile ステージ設計
     Checkout:GitHub リポジトリからコード取得
     Prepare Release:Appサーバーにディレクトリ作成、現在のコードコピー
     Update Environment:Secrets Manager から .env 取得
     Install Dependencies:Python / Node.js の依存関係をインストール
     Deploy:成果物を App サーバーに配置、シンボリックリンク切替、サービス再起動
     Cleanup Old Releases:古いリリース削除


使用環境

☁️EC2

  • Jenkinsサーバー: Amazon Linux 2023, t2.medium, 8GBストレージ, privateサブネットに配置
  • Appサーバー:Amazon Linux 2023, t2.micro, 8GBストレージ, privateサブネットに配置
  • 踏み台:既存踏み台利用

🛜VPC

  • プライベートサブネットに配置、Jenkins サーバーと App サーバーは踏み台経由でアクセス

🔐セキュリティーグループ(インバウンド)

  • Jenkinsサーバー
    ポート:8080, 22
    許可元:踏み台のプライベートIP(もしくは VPC 内の CIDR)

  • APPサーバー
    ポート: 22
    許可元:踏み台のプライベートIP(もしくは VPC 内の CIDR)
        JenkinsサーバープライベートIP
    ポート:5050
    許可元:踏み台のプライベートIP(もしくは VPC 内の CIDR)

これから構築の手順を順に解説していきます。
作業中の躓きやポイントも随時紹介します。
※手順は学習のアウトプットとして整理した内容です。そのまま実行して同じ環境が作れるわけではありませんのでご注意ください。

構築手順

①Jenkinsインストール(Jenkins サーバー上で実施) &初期設定

  • システム更新 & Java(Amazon Corretto 21)インストール
sudo yum update -y
sudo dnf install -y java-21-amazon-corretto-devel.x86_64
  • Jenkins のリポジトリ追加 & インストール
sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
sudo dnf install -y jenkins
  • Jenkins サービスを有効化 & 起動
sudo systemctl enable jenkins
sudo systemctl start jenkins
sudo systemctl status jenkins   # Active: active になっていればOK
  • 初期パスワード確認(Web UI 初回ログインに使用)
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
  • 踏み台経由でポートフォワード(Mac ローカルで実施)
ssh -i ~/Downloads/踏み台SSH用.pem -L 8888:〈JenkinsサーバープライベートIP〉:8080 ec2-user@〈踏み台パブリックIP〉
  • アクセス確認

http://localhost:8888

  • Jenkins初期設定

  1. 先ほどの初期パスワードでアンロック
  2. 「Install suggested plugins」を選択
  3. Jenkins ユーザー作成
    インストール後の初期画面.png
    スクリーンショット 2025-09-22 15.43.34.png

②GitHub 用の認証情報(credentialsId)を登録

Jenkins が GitHub リポジトリにアクセスできるように、SSH鍵を作成し、Jenkins に登録します。🔑

  • credentialsId とは
    Jenkins に保存してある「認証情報」のこと。
    ジョブや Jenkinsfile 内で指定すると、認証済みの状態で外部サービス(GitHub など)にアクセス可能
  • GitHub 用の SSH 鍵を作成(Jenkins サーバー上で実施)
sudo -u jenkins ssh-keygen -t rsa -b 4096 -f /var/lib/jenkins/.ssh/id_rsa_github -N ""
  • 公開鍵の確認(GitHub に登録)
sudo cat /var/lib/jenkins/.ssh/id_rsa_github.pub
  • GitHub のホストキーを known_hosts に登録

sudo -u jenkins bash -c 'ssh-keyscan github.com >> /var/lib/jenkins/.ssh/known_hosts'

ー GitHub に公開鍵を登録


  1. GitHub: Settings > SSH and GPG keys > New SSH key
  2. id_rsa_github.pub の内容を貼り付けて保存💾
  3. 登録完了すると下記画像のように表示されます👇
    スクリーンショット 2025-09-22 15.35.26.png
  • Jenkins に秘密鍵を登録(credentialsId)
    Jenkins UIで実施
  1. ダッシュボード > Jenkinsの管理 > Credentials > Global > Add Credentials
  2. 種類:SSHユーザ名と秘密鍵
     ユーザー名    :git
     秘密鍵      :id_rsa_github の内容をコピペ
     ID(credentialsId):github-ssh

ID名は Jenkinsfile 内で記載するものと同じ名前でないとエラーになります⚠️
筆者はまんまとミスって変更しました😅



③ Appサーバー用 SSH 鍵の登録

Jenkins が Appサーバーにデプロイできるように、専用の SSH 鍵 を作成します。


  • GitHub 用の SSH 鍵とは別物です(混同しないように‼️)
  • ここで作るのは「Jenkins → Appサーバー接続用」の鍵です
    恥ずかしながら私は、手順作成しながら、
    「この鍵がGithubで、こっちがApp接続用で….😵‍💫」となりました😂
  • Jenkins ユーザの .ssh ディレクトリ作成(Jenkins サーバー上で実施)
sudo -u jenkins mkdir -p /var/lib/jenkins/.ssh
  • 秘密鍵の生成(Jenkinsユーザ用)

sudo -u jenkins ssh-keygen -t rsa -b 4096 -f /var/lib/jenkins/.ssh/id_rsa -N ""

  • SSH Config に Appサーバーの接続設定を追加
sudo -u jenkins tee /var/lib/jenkins/.ssh/config > /dev/null <<EOF
Host app-server
  HostName AppサーバープライベートIP
  User deploy-user
  IdentityFile /var/lib/jenkins/.ssh/id_rsa
EOF

  • Appサーバーのホストキーを known_hosts に登録

sudo -u jenkins ssh-keyscan -H app-server >> /var/lib/jenkins/.ssh/known_hosts

  • Appサーバーに公開鍵を登録(Appサーバー上で実施)
sudo su - <deploy-user>

mkdir -p ~/.ssh
vi ~/.ssh/authorized_keys
# → Jenkinsサーバーの /var/lib/jenkins/.ssh/id_rsa.pub の内容を貼り付け
  • パーミッション調整(Jenkins サーバー上で実施)
sudo chown -R jenkins:jenkins /var/lib/jenkins/.ssh
sudo chmod 700 /var/lib/jenkins/.ssh
sudo chmod 600 /var/lib/jenkins/.ssh/config
sudo chmod 600 /var/lib/jenkins/.ssh/id_rsa


補足

Appサーバーへの鍵登録はもっとスマートな方法があるような気がしますが、今の私の技量ではこれが最善と判断しました💦


④Jenkins プラグインのインストール


Jenkins Pipeline で GitHub や AWS、Node.js を扱うために必要なプラグインをインストールします。
※元々、インストール済みのものもあるので、1つずつ確認しながら実施。

  • インストール手順(Jenkins UI)
  1. Jenkins の管理 → Plugins → Available Plugins

  2. 検索ボックス: 「プラグイン名」
  3. インストール → 必要に応じて Jenkins を再起動(Jenkins 管理 → 設定の再読み込み)


プラグイン.png

  • 必要なプラグイン

1, Pipeline: AWS Steps
 
→Pipeline 内で AWS 認証情報を簡単に利用する



2, NodeJS Plugin
→Jenkins から Node.js / npm を使えるようにする


Jenkins 管理 → Tools で「登録名」を設定します。
Jenkinsfile 内で NodeJS を参照する際、この名前と同じにする必要があります。
私は今回、npm コマンドを使う際 Node.js 24 系最新版で試しましたが、プロジェクト依存関係でエラーが出ました💦
事前に App サーバーの Node.js バージョンを確認して、同じバージョンを Tools に登録することが必須です。

node .png

3, Git plugin
 
→GitHub リポジトリを clone / fetch する

4, GitHub plugin
 
→GitHub Webhook や PR トリガーを利用
  
※今回は不要ですが、将来使うかもしれないので一応インストールしても良いです。
個人的には「まだ使わないけど念のため」インストールしました😅


5, Credentials plugin
→SSH 鍵や AWS キーなどの認証情報を管理

6, Credentials Binding plugin
→Jenkinsfile 内で環境変数として認証情報を注入

7, Pipeline
→Jenkins の Pipeline 機能を利用する(ほとんどの環境ではデフォルトでインストール済み)



⑤ AWS 用 credentialsId 登録 & IAM 権限付与



Jenkins が Secrets Manager から .env を取得できるように、AWS 認証情報を登録します。
この手順は機密情報を扱うため、取り扱い注意🚨

  • 目的

    Jenkinsfile 内で withAWS(credentials: 'aws-cred-id') を使い、Pipeline から安全に AWS にアクセスするため。
  • 前提
    Jenkins に Credentials Binding PluginPipeline: AWS Steps がインストール済みであること。

  • AWS 側での設定(IAM)
    →Secrets Manager のシークレットにアクセスするための IAM ポリシーを作成。
     KMS で暗号化されている場合はカスタム KMS キーに対して kms:Decrypt 権限が必要。
    AWS が提供するデフォルトキー(aws/secretsmanager)の場合は不要。

  • IAM設定内容

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["secretsmanager:GetSecretValue"],
      "Resource": ["arn:aws:secretsmanager:AWS_REGION:YOUR_ACCOUNT_ID:secret:Secrets Manager 登録名*”]
    }
  ]
}

YOUR_ACCOUNT_ID:AWS アカウントの 12 桁 ID(AWS管理画面右上のプロフィールから確認可能)

今回は Jenkins から AWS にアクセスする方法として、2 つのやり方を扱いました。

  • 実際の構築では、既存のインスタンスロールを Jenkins サーバーに割り当てて利用しています。
  • 学習のために、credentialsId を Jenkins に登録してアクセスさせる手順も確認しました。

・Jenkins 側での登録(UI)

  1. Jenkins ダッシュボード → Manage Jenkins → Credentials → Add Credentials
  2. Kind: AWS Credentials
  3. Access Key / Secret を入力
      ID(credentialsId): aws-cred-id
      Access Key ID  :IAM ユーザーのアクセスキー
      Secret Access Key:IAM ユーザーのシークレットキー

  • Secret Access Key は IAM ユーザー作成時に生成される一度きりの値です。必ず控える。
  • 他人に見せてはいけない非常にセンシティブな情報です⚠️
  • ID 名 aws-cred-id は Jenkinsfile 内で参照します。名前を間違えると Pipeline でエラーになります⚠️
  • IAM ポリシーの設定が正しくないと Secrets Manager から .env が取得できません。

スクリーンショット 2025-09-22 15.48.33.png


⑥ AWS CLI / jq / git のインストール

aws cli と git は元々Amazon Linux 2023起動時点で存在しており、バージョンを確認しました。
(Pipeline 内で AWS や GitHub を扱うために必要)

  • jq のインストール(Jenkins サーバー上で実施)
sudo yum -y install jq

  • AWS CLI v2 のバージョン確認

Amazon Linux 2023 ではデフォルトでインストール済み。

aws --version
  • git バージョン確認(jenkinsユーザーで実行できるか確認)
 sudo -u jenkins git --version

  • AWS CLI
    → Jenkins の中から AWS を操作するために必須。
     特に aws secretsmanager get-secret-value などで Secrets Manager から値を取得する際に使用。
  • jq
    → AWS CLI の JSON 出力から必要な値だけを抽出するのに便利。
  • git
    → Jenkins が GitHub からソースコードを取得するのに必要。


⑦ Jenkins実行ユーザ(deploy-user)に sudoを許可

  • 目的
    Jenkins からアプリのサービスを再起動できるように、 〈deploy-user〉 に必要最小限の sudo 権限を与える。
    ここの理解は前段でも話した、「どこで」 「誰が」 「何を」 を抑えるために必要でした!
    またこのプロセスを経て、systemd管理でfun-siteサービスが登録されており、gunicornの起動停止管理をしていることもわかりました。
  • systemctl のパスを確認(appサーバー上で実施)
which systemctl
# 出力例: /usr/bin/systemctl
  • sudoers ファイル作成

sudo visudo -f /etc/sudoers.d/deploy-user


# 以下を追記(/usr/bin/systemctl の部分は上記の which 結果に合わせること)
# deploy-userが fun-site のサービス操作をパスワード無しで実行できる
 deploy-user ALL=(root) NOPASSWD: /usr/bin/systemctl restart fun-site, \
                               /usr/bin/systemctl start fun-site, \
                               /usr/bin/systemctl stop fun-site, \
                               /usr/bin/systemctl status fun-site, \
                               /usr/bin/rm, \
                               /usr/bin/ln
  • パーミッション確認

sudo chmod 440 /etc/sudoers.d/deploy-user
  • 動作テスト

# Jenkins 実行ユーザから sudo 経由で操作できるか確認
sudo -u  deploy-user sudo /usr/bin/systemctl status fun-site
sudo -u  deploy-user sudo /usr/bin/rm --version
sudo -u  deploy-user sudo /usr/bin/ln --version


⑧ GitHub への Jenkinsfile 配置



ついにやってまいりました、Jenkinsfile…。
一番の難敵です。今回の構成は、
GitHubから Jenkinsfile を取得 → ビルド → Appサーバーへ資材を移動 → Appサーバー更新 を行います。
修正、修正、また修正の繰り返しでした😂
Jenkinsfileの配置はこのような感じで実施しました👉


  • Githubからclone
mkdir ~/workspace
cd ~/workspace
git clone git@github.com:your-org/my-app-repo.git
cd my-app-repo
  • 作業用ブランチ作成

git checkout -b infra/tacos


・Jenkinsfileの作成

vi Jenkinsfile




pipeline {
  agent any
  tools {
    nodejs "NodeJS"
  }
  environment {
    AWS_REGION  = 'AWS_REGION'
    SECRET_NAME = 'SECRET_NAME'
  }
  stages {
    stage('Checkout') {
      steps {
        git branch: 'main',
            url: 'git-repo-url',
            credentialsId: 'github-ssh'
      }
    }

    stage('Prepare Release') {
      steps {
        script {
          def deployUser = "deploy-user"
          def deployHost = "app-server"
          def releaseDir = "/home/deploy-user/release_Dir/${sh(script: 'date +%Y%m%d', returnStdout: true).trim()}"

          echo "[INFO] Creating release directory on App Server and copying current contents"
          sh """
            ssh ${deployUser}@${deployHost} '
              mkdir -p ${releaseDir};
              cp -pR current-dir/* ${releaseDir}/;
              exit
            '
          """

          echo "[INFO] Release directory prepared: ${releaseDir}"
        }
      }
    }

    stage('Install Python Dependencies') {
      steps {
        script {
          def deployUser = "deploy-user"
          def deployHost = "app-server"
          def releaseDir = "/home/deploy-user/release_Dir/${sh(script: 'date +%Y%m%d', returnStdout: true).trim()}"

          echo "[INFO] Copying requirements.txt to App Server and installing dependencies"
          sh """
            scp requirements.txt ${deployUser}@${deployHost}:${releaseDir}/
            ssh ${deployUser}@${deployHost} '
              cd ${releaseDir}
              pip install --no-cache-dir -r requirements.txt
              exit
            '
          """

          echo "[INFO] Building frontend locally"
          sh """
            cd static-dir
            npm i
            npm run local:build
          """
        }
      }
    }

    stage('Update Environment') {
      steps {
        withAWS(region: "${AWS_REGION}", credentials: 'aws-cred-id') {
          sh '''
            echo "[INFO] Fetching secrets from AWS Secrets Manager"
            secret_json=$(aws secretsmanager get-secret-value \
              --region "${AWS_REGION}" \
              --secret-id "${SECRET_NAME}" \
              --query SecretString --output text)
            echo "$secret_json" | jq -r 'to_entries[] | "\\(.key)=\\(.value)"' > .env
            echo "[INFO] .env created"
          '''
        }
      }
    }

    stage('Deploy') {
      steps {
        script {
          def deployUser = "deploy-user"
          def deployHost = "app-server"
          def releaseDir = "/home/deploy-user/release_Dir/${sh(script: 'date +%Y%m%d', returnStdout: true).trim()}"

          echo "[INFO] Deploying build to App Server"
          sh """
            rsync -avz ./ ${deployUser}@${deployHost}:${releaseDir}/
          """

          echo "[INFO] Switching current symlink and restarting service"
          sh """
            ssh ${deployUser}@${deployHost} '
              sudo ln -sfn ${releaseDir} current-symlink
              sudo systemctl restart service-name
              exit
            '
          """
        }
      }
    }

    stage('Cleanup Old Releases') {
      steps {
        script {
          def deployUser = "deploy-user"
          def deployHost = "app-server"

          echo "[INFO] Cleaning old releases"
          sh """
            ssh ${deployUser}@${deployHost} '
              cd /home/deploy-user/release_Dir
              ls -1dt */ | tail -n +6 | xargs -r sudo rm -rf
              exit
            '
          """
        }
      }
    }
  }
}


・Githubへのpush


git add Jenkinsfile
git commit -m "infra: add Jenkinsfile for CI/CD pipeline"
git push origin infra/tacos

⑨Jenkins ジョブ(Pipeline)の設定

Jenkins で GitHub から Jenkinsfile を取得して自動実行するための設定手順です。

  1. 新規ジョブ作成
    Jenkins UI → 「新規ジョブ作成」 → パイプライン を選択

  2. パイプラインの取得方法
    Pipeline → Pipeline script from SCM を選択

  3. SCM の設定
    • SCM: Git
    • Repository URL: git@github.com:your-org/my-app-repo.git
    • Credentials: github-ssh
    • Branch: main

  4. Jenkinsfile の実行
    設定が完了すると、Jenkins が指定リポジトリから Jenkinsfile を取得し、自動でジョブを実行します

名称未設定のデザイン (4).png


⑩Jenkins ジョブ(Pipeline)の設定

Jenkins で GitHub リポジトリの更新を自動で検知し、ジョブを実行するための設定です。

  1. ジョブ設定画面を開く
    Jenkins → 対象ジョブ → 「設定(Configure)」をクリック
  2. ビルドトリガーを選択
    「ビルド・トリガー(Build Triggers)」セクションへ
    「SCM のポーリング(Poll SCM)」にチェック
  3. ポーリング間隔の指定
    「スケジュール(Schedule)」欄に Cron 形式で指定
    例:5分ごとにチェック
H/5 * * * *

4.設定を保存

💡 ポイント
ポーリング間隔は環境や運用に応じて調整可能。
「短すぎるとサーバー負荷が増える」「長すぎると反映が遅くなる」ので、間隔については実際の運用に合わせて設定する必要がある。


実際にハマったポイントまとめ



  • リリースディレクトリの扱い

    最初、既存ディレクトリに上書きしてしまい、Git管理外の静的ファイルが消えてページが真っ白に。
    日付付きディレクトリを作り、シンボリックリンクで切り替える仕組みを正しく理解する大事さを痛感。
  • npm バージョンの不一致
    Node.js バージョンが合わずビルド失敗。
    Tools の登録時に 本番サーバーと合わせるのが鉄則!
  • sudo 権限の不足
    サービス再起動コマンドを Jenkins から実行しようとしたら「Permission denied」…。
    deploy-user に sudo を付け忘れてました。
    これは「誰が」「どこで」「何を」実行するのかを理解してなかった典型例ですね。
  • GitHub credentialsId の設定ミス
    Jenkinsfile と Jenkins UI の ID が一致していないと Git clone が失敗。
    名前を必ず揃えること。
    UI と Jenkinsfile の名前をきちんと揃える、これ本当に大事です!
  • Jenkins サーバースペック不足
    最初は t2.micro で構築しましたが、ビルド時にメモリ不足で失敗…。
    t2.medium に変更したところ安定しました。
    Jenkins は最初から余裕のあるインスタンスサイズを選ぶのが大事です。

最後に

ここまでお付き合いいただきありがとうございました。

第1章でも書きましたが、big-Aからの

「作業者ではなく、技術者を目指しなさい」

この言葉を目標に、自分で考え、自分の作業をアウトプットとして Qiita に書くことで、作業者から技術者に一歩でも近づけるのではないかと思い、色々と書き加えました。

改めて Jenkins についてまとめてみて、自分の技術力や配慮の足りなさ、Jenkinsそのものやインフラエンジニアとしての理解がまだまだ足りないことを実感しました。
成功し続ける人はいないと考えており、失敗から学べることもたくさんあります。今回の経験を次に活かし、同じ作業や似た作業をしたときに役立てたいと思っています。

皆さんも、失敗するたびに下を向くのではなく、その失敗を次に活かせる形にしてみてください😊

ここまでご覧いただきありがとうございました。
また次の記事でお会いしましょう。

Valeu! 👋

Information
第1章で話していたスポーツ選手向けのファンサイトは下記URLより閲覧いただけます。
まだ小規模でテスト的なサイトですが、見ていただけると嬉しいです。
https://www.ultra-fanatico.com/

また、私たちの自社ホームページもぜひご覧ください。🙇
https://www.huracan.co.jp/

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?