第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〉
- アクセス確認
- Jenkins初期設定
②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 に公開鍵を登録
- GitHub: Settings > SSH and GPG keys > New SSH key
- id_rsa_github.pub の内容を貼り付けて保存💾
- 登録完了すると下記画像のように表示されます👇
- Jenkins に秘密鍵を登録(credentialsId)
Jenkins UIで実施
- ダッシュボード > Jenkinsの管理 > Credentials > Global > Add Credentials
- 種類: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)
- Jenkins の管理 → Plugins → Available Plugins
- 検索ボックス: 「プラグイン名」
- インストール → 必要に応じて Jenkins を再起動(Jenkins 管理 → 設定の再読み込み)
- 必要なプラグイン
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 に登録することが必須です。
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 Plugin と Pipeline: 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)
- Jenkins ダッシュボード → Manage Jenkins → Credentials → Add Credentials
- Kind: AWS Credentials
- 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 が取得できません。
⑥ 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 を取得して自動実行するための設定手順です。
-
新規ジョブ作成
Jenkins UI → 「新規ジョブ作成」 → パイプライン を選択 -
パイプラインの取得方法
Pipeline → Pipeline script from SCM を選択 -
SCM の設定
• SCM: Git
• Repository URL: git@github.com:your-org/my-app-repo.git
• Credentials: github-ssh
• Branch: main -
Jenkinsfile の実行
設定が完了すると、Jenkins が指定リポジトリから Jenkinsfile を取得し、自動でジョブを実行します
⑩Jenkins ジョブ(Pipeline)の設定
Jenkins で GitHub リポジトリの更新を自動で検知し、ジョブを実行するための設定です。
- ジョブ設定画面を開く
Jenkins → 対象ジョブ → 「設定(Configure)」をクリック - ビルドトリガーを選択
「ビルド・トリガー(Build Triggers)」セクションへ
「SCM のポーリング(Poll SCM)」にチェック - ポーリング間隔の指定
「スケジュール(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 は最初から余裕のあるインスタンスサイズを選ぶのが大事です。
- Jenkins tmpdir の容量不足
Jenkins の tmpdir(Java 一時ディレクトリ)の容量が足りないと、ノードがオフラインになりビルドができなくなる場合があります。
自分の環境では tmpdir の容量が少なかったため、既存の容量が十分なディレクトリを参照先として設定しました。
手順はこちらの記事を参考しました。
https://qiita.com/tamorieeeen/items/4cdd17e00cf3bf1c0822#:~:text=6.%20tmpdir%E3%81%AE-,%E5%A4%89%E6%9B%B4,-tmpdir%E3%81%AE%E5%AE%B9%E9%87%8F
最後に
ここまでお付き合いいただきありがとうございました。
第1章でも書きましたが、big-Aからの
「作業者ではなく、技術者を目指しなさい」
この言葉を目標に、自分で考え、自分の作業をアウトプットとして Qiita に書くことで、作業者から技術者に一歩でも近づけるのではないかと思い、色々と書き加えました。
改めて Jenkins についてまとめてみて、自分の技術力や配慮の足りなさ、Jenkinsそのものやインフラエンジニアとしての理解がまだまだ足りないことを実感しました。
成功し続ける人はいないと考えており、失敗から学べることもたくさんあります。今回の経験を次に活かし、同じ作業や似た作業をしたときに役立てたいと思っています。
皆さんも、失敗するたびに下を向くのではなく、その失敗を次に活かせる形にしてみてください😊
ここまでご覧いただきありがとうございました。
また次の記事でお会いしましょう。
Valeu! 👋
Information
第1章で話していたスポーツ選手向けのファンサイトは下記URLより閲覧いただけます。
まだ小規模でテスト的なサイトですが、見ていただけると嬉しいです。
https://www.ultra-fanatico.com/
また、私たちの自社ホームページもぜひご覧ください。🙇
https://www.huracan.co.jp/





