DevOps

Dockerを用いたJenkins+Nginx+SSL化の作業メモ

今回はCI/CDに使われるJenkinsサーバーの環境構築について取り上げます。

 

Jenkinsを使うだけであればGoogle Cloud Platform Marketplaceなどで簡単に構築することができますが、今回は学習目的も兼ねてイチからUbuntu Serverに環境を構築していきます。

こんな方におすすめ

  • Jenkinsサーバーの構築方法を知りたい方
  • Docker + Jenkins + NginxのSSL化で詰まってしまった方

なおこの記事では全体の作業フローの解説が主たる目的のため、Dockerなど個別の技術の解説は行いません。またJenkinsの具体的な使い方に関しても解説は行いません。あくまで環境構築の記事になります。

 

必要なもの

まずはJenkinsサーバー構築のために必要なものを整理しておきます。

  • 有料または無料のサーバー環境
  • 独自ドメイン

サーバー環境はそれぞれ好みの環境で構いませんが、Dockerとの相性を考えるとOSはUbuntuがおすすめです。

当記事ではGoogle Cloud Platform(GCP)のCompute Engineを使用します。使ったことの無い方であれば一年分使える$300クレジットが使えるため、一年間は実質無料で使えます。GCPを使う方は料金体系や無料枠に関して目を通しておきましょう。

 

また、SSL/TLS化をするため、独自ドメインを契約しておく必要があります。

お名前ドットコム、さくらのドメイン、Google Domainなんでも構いません。

 

手順

大まかな流れとしては以下の通りです。

  1. サーバーのセットアップ
  2. Dockerセットアップ
  3. ドメインの準備
  4. Nginxリバースプロキシ構築+SSL化
  5. Jenkins起動
  6. Jenkinsを使い始める

それぞれ解説していきます。

 

サーバーのセットアップ

サーバーを用意

上でも述べた通り、VPSでもGCPのような無料のサービスでも何でも良いです。

Google Compute Engineを使う場合は最小構成であるN1 シリーズ・マシンタイプf1-microではメモリ不足になる(と思われる)ので少なくともg1-small以上が良いと思います。

私の場合、GCP無料枠を考慮しf1-microで試してみましたが、結局メモリ不足になってしまったためg1-smallへ拡張しました。

 

GCPを利用する場合はgcloudというコマンドラインツールを利用すると便利です。ブラウザからコンソールを起動することもできますが、レスポンスがやや悪いです。

 

サーバーにUbuntuをインストール

上でも述べましたが、OSに関してはUbuntuがオススメです。CentOS7ではDockerをyumでインストールしてもそのままでは使えませんし(おそらくSELinuxの関係)、CentOS8以降ではpodmanが推奨になり公式リポジトリには入っていません。

 

今回はUbuntu 20.04 LTSを前提に進めます

最低限のセキュリティ上の対応

ここは各々の判断によりますが、最低限のセキュリティ上の対応はしておきます。

sshの設定

デフォルトではsshは22番ポートを利用しますが、変更しておきます。変更先は自由に利用できるポート番号(ググって下さい。50000~60000あたりが無難)を割り当てます。

/etc/ssh/sshd_configのPort部分を変更します。ここでは仮に54321に変更することとします。

# Port 22
Port 54321

*まだsshdのrestartはしません。

加えてパスワード認証とrootログインも無効化しておきます。

パスワード認証の無効化

PasswordAuthentication no

rootログインの無効化

PermitRootLogin no

最後に設定を反映させるためsshdをリスタートさせます。

sudo systemctl restart sshd

 

gcloudを使う場合の補足

sshのポートを変更した場合、GCP側のFirewall設定も変更する必要があります。

GCEメニューの「ネットワークの詳細の表示」を選択

ページ内の「default-allow-ssh」を選択。

「プロトコルとポート」からポートを変更します。

またgcloudも通常のコマンドではssh接続ができなくなります。メニューの「gcloudコマンドを表示」からコマンドをコピーし「beta」を削除、コマンドの最後に「--ssh-flag="-p ポート番号"」を加えます。

gcloudコマンド例:

gcloud compute ssh --zone "us-west1-b" "instance-name" --project "project-name" --ssh-flag="-p 54321"

これでカスタムポートでssh接続が可能です。

 

firewallの設定

次にfirewallを有効化します。ubuntuの場合ufw(Uncomplicated FireWall)を使います。

まずはufwの状態を確認します。おそらくinactiveになっているはずです。

sudo ufw status

変更したsshのポートを許可します。

sudo ufw allow 54321

 

次にhttp, httpsのために80, 443ポートを開放します。

sudo ufw allow 80
sudo ufw allow 443

最後にufwを有効化させます。

sudo ufw enable

念の為ufw statusで確認しておいて下さい。

 

gcloudを使う場合の補足

sshと同様にGCP側のFirewall設定も変更する必要があります。http, httpsに関しては「VMインスタンス詳細」画面からチェックボックスにチェックを入れるだけです。

 

Dockerのセットアップ

次にDocker関係のインストールをします。

Dockerのインストール

パッケージ類の更新をしておいて下さい。

sudo apt update

Dockerをインストールします。

補足

Dockerをインストールする方法はリポジトリを追加する方法などいくつかありますが、今回は最も簡単な方法を選択します。

sudo apt install docker.io

dockerのhello-worldで動作確認します。

sudo docker container run hello-world

 

またサービスの自動起動の設定もしておきましょう。

sudo systemctl enable docker

 

Docker Composeのインストール

Docker Composeに関してはバージョンによって動きが変わったりするので公式ドキュメントを参考にインストールします。

 

ドメインの準備

次にドメインを準備します。(私はGoogle Domainでドメインを取得し、サブドメインを設定しました。)

ネームサーバーの設定などは業者によって方法は異なりますが、digコマンド等を利用してドメイン名からIPアドレスが取得できることを確認してください

例:

dig sub.domain.com

 

注意ポイント

GCPを利用する場合はIPアドレスを固定化するために追加の作業が必要です。

GCPを利用する場合、VMを起動するたびにIPアドレスが変わってしまうため、IPアドレスを固定する必要があります。(「VPSネットワーク」→「外部IPアドレス」→「静的アドレスを予約」でアドレスをVMに割り当ててください)

GCP以外の通常のVPSを使用している場合は問題ありません。

 

Nginxによるリバースプロキシ構築+SSL/TLS認証

Nginxによるリバースプロキシを構築します。

Let's EncryptによってSSL/TLS認証を行います。ACMEクライアントに関してはCertbotを使用します。CertbotはDocker Imageも公開されています。

補足

ちなみにNginxをリバースプロキシとして使い、かつSSL化を前提としたDocker Imageはいくつか公開されており、それらを使用する方が便利です。

例えばjwilder/nginx-proxyの「SSL Support using letsencrypt」のあたりを参考に。

ただし今回は学習目的も兼ねて公式のImageを使っていきますので、以下の記事を参考にします。

この記事は(タイトル通り)5分で出来るとは言わないまでも、簡潔に説明されています。読みたくないという方は記事に載っている通りGitHubをまず見てみるといいでしょう。

 

基本的には上の記事通りでいいのですが、いくつか変更した箇所や上手く動かなかった箇所もあります。

(必要であれば記事に出てくるdocker-compose.ymlにnetworkやrestartを指定して下さい。)

変更点は以下の通り。

  • Nginxの設定ファイルはvolumeを使うのではなくDockerfileでコピーする
  • Jenkins用のNginx設定ファイルはJenkinsのWikiを参考にする

詳細は以下の通り。

Dockerfile

Dockerfileは以下のように設定しました。

FROM nginx:1.17.10
EXPOSE 80 443
RUN rm /etc/nginx/conf.d/default.conf
# nginx.conf内でsites-enabledを読み込むように変更
COPY nginx.conf /etc/nginx/nginx.conf
WORKDIR /etc/nginx/sites-enabled
COPY my.site.conf .

my.site.confはjenkins用の設定ファイルで、これをsites-enabledフォルダにコピーします。このsites-enabledを読み込むように変更したnginx.confを用意しておきます。

例:

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

一番最後にsites-enabledを読み込むように設定しています。

 

Jenkins用のNginx設定ファイル

Jenkins WikiにNginxの設定ファイルが公開されています。

こちらの「Running from a subdomain with SSL」の箇所をとりあえずコピペします。

ただしssl部分に関してはcertbot用の設定をする必要があります。(Nginx and Let’s Encrypt with Docker in Less Than 5 Minutesの通りに)

sub.domain.comの箇所をご自分のドメインに置き換えてください。

# ssl_certificate /etc/nginx/ssl/server.crt;
# ssl_certificate_key /etc/nginx/ssl/server.key;
ssl_certificate /etc/letsencrypt/live/sub.domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/sub.domain.com/privkey.pem;

 

コンテナ起動

記事中のinit-letsencrypt.shを実行することでSSL認証のチャレンジがされ、docker-compose up がされます。なおinit-letsencrypt.sh中のメールアドレス・ドメイン名は適宜書き換えて下さい。

記事中にもあるようにこのスクリプトは

  1. ダミーの認証情報を作成
  2. Nginxを起動
  3. ダミーの認証情報を削除し、実際の認証リクエストを行う

ということを行っています。

 

エラー&対処法

ここまではいいのですが、ここから先はややハマりました。

Let's Encrypt certificateでエラー

認証が成功せず下記のようなエラーが出ます。

### Requesting Let's Encrypt certificate for (ドメイン名) ...
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator webroot, Installer None
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for (ドメイン名)
Using the webroot path /var/www/certbot for all unmatched domains.
Waiting for verification...
Challenge failed for domain (ドメイン名)
http-01 challenge for (ドメイン名)
Cleaning up challenges
Some challenges have failed.

IMPORTANT NOTES:
 - The following errors were reported by the server:

   Domain: (ドメイン名)
   Type:   unauthorized
   Detail: Invalid response from
   https://(ドメイン名)/.well-known/acme-challenge/(略)
   [IPアドレス]: "<html>\r\n<head><title>502 Bad
   Gateway</title></head>\r\n<body>\r\n<center><h1>502 Bad
   Gateway</h1></center>\r\n<hr><center>nginx/1.17.1"

   To fix these errors, please make sure that your domain name was
   entered correctly and the DNS A/AAAA record(s) for that domain
   contain(s) the right IP address.

結論から言えば以下のコードをhttps(443ポート)の方のディレクティブに指定することで動くようになりました。

location /.well-known/acme-challenge/ {
    root /var/www/certbot;
}

認証チャレンジはhttpの方つまり80ポートの方でやりとりをする(と私は認識していた)のですがhttpsの方に来ています。

原因は(おそらく)私がdevドメインを使用していたことにあるようです。

補足

devドメインや他の一部のドメインではSSL化が必須です。(httpによるサイトへのアクセスが出来ません)

このLet's Encryptのページを見るとこのように書いてあります。

The “HTTPS only” policy for .dev is enforced by browsers, not by any other part of the Internet, so you can and should have an HTTP listener on your web server. Most browsers won’t ever talk to it, but the Let’s Encrypt validation bot will (if you use the HTTP-01 challenge method).

Google 翻訳

.devの「HTTPSのみ」のポリシーは、インターネットの他の部分ではなくブラウザによって適用されるため、WebサーバーにHTTPリスナーを配置できます。 ほとんどのブラウザーは通信しませんが、Let’s Encrypt検証ボットは通信します(HTTP-01チャレンジ方式を使用している場合)。

これが原因かなと思っていますが、間違っていた場合は訂正します。

 

Jenkins起動

ここまででNginxのサーバ自体は正しく動いていると思います。

次に別のdocker-compose.ymlでJenkinsを動かしたいと思います。

例:

version: "3.8"
services:
  jenkins:
    image: jenkins/jenkins:2.234
    restart: unless-stopped
    ports:
      - "8080:8080"
      - "50000:50000"
    volumes:
      - jenkins_home:/var/jenkins_home

volumes:
  jenkins_home:

networks:
  default:
    external:
      name: nginxで使っているnetwork

 

これでJenkinsの起動自体はするのですが、Nginx経由ではブラウザでアクセス出来ないと思います。

問題:ブラウザでアクセスすると502 Bad Gateway

ブラウザで表示してみるとSSL/TLS認証は出来ているのですが、Jenkinsのページは表示されず502 Bad Gatewayが表示されます。

Nginxのログを見ても、127.0.0.1:8080にアクセス出来ていない様子。

 

これはNginx設定ファイルのupstream部分が間違っていることが原因でした。

upstream jenkins {
  server 127.0.0.1:8080 fail_timeout=0;
}

NginxとJenkinsをネイティブインストールする場合は問題ないのですが、Dockerを使う場合127.0.0.1はホスト側ではなくコンテナ自体を指してしまうため機能しないようです。

詳しくは下記が参考になります。

ただし上のstackoverflowのようにIPアドレスを指定するのではなく、docker-compose.ymlで指定したサービス名を指定します。

upstream jenkins {
  server jenkins:8080 fail_timeout=0;
}

これでJenkinsのセットアップ画面が表示されるようになります。

 

Jenkinsセットアップ

Jenkinsのセットアップ画面が表示されたら初期パスワードを確認します。ブラウザ上にパスワードが入っているフォルダ名が表示されるので以下のコマンドでコンテナに入って確認。

sudo docker container exec -it jenkinsのコンテナ名 bash

あとはユーザ名・パスワードの設定やプラグインのインストールがありますが、ここから先は使用用途によって変わりますので、これにて環境構築は完了となります。

 

 

まとめ

今回はUbuntu ServerにDockerを用いてJenkinsサーバーを構築しました。Nginxによるリバースプロキシを用い、かつLet's Encrypt + (ACMEクライアントとして)CertbotによるSSL/TLS化をしました。

GCPは無料枠がありますし、ドメイン名は安く取得が可能ですのでぜひ試してみて下さい。

 

今回は他記事を参考に進めたためソースコードを公開することはしませんが、大まかな流れを参考にしてもらえればと思います。

 

 

この記事は参考になりましたか?

下記のボタンよりアンケートにご協力お願いします。

-DevOps
-, ,

© 2020 エンジニアの本棚