CLOVER🍀

That was when it all began.

Ubuntu Linux 18.04 LTSにstunnelをインストールする

これは、なにをしたくて書いたもの?

SSLTLSのトンネリングツールである、stunnelをちょっと試してみたいなぁと思いまして。

SSLTLSなクライアント、サーバー間の通信を、SSLTLSに対応させる手段としては知っておいてもよいかな、と。

stunnel

最初に書きましたが、SSLTLSのトンネリングツールです。

stunnel: Home

SSLTLSに対応していないクラアントやサーバーの間にプロキシ的に配置することで、間の通信を暗号化することができます。

機能詳解は、こちら。

stunnel: Features

クロスプラットフォームで動作し、各プラットフォームに特化した機能もあります。

ドキュメントやExampleはこちら。

stunnel: Documentation

stunnel: Unix Config

stunnel: Windows Config

設定内容は、Exampleを見るとある程度想像できそうな感じですね。

その他の参考情報。

4.8. stunnel の使用 Red Hat Enterprise Linux 7 | Red Hat Customer Portal

@IT:Security Tips > SSL非対応メーラのSSL化

環境

今回の環境は、こちら。Ubuntu Linux 18.04 LTSです。

$ uname -srvmpio
Linux 4.15.0-96-generic #97-Ubuntu SMP Wed Apr 1 03:25:46 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux


$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.4 LTS
Release:    18.04
Codename:   bionic

インストール

では、stunnelをインストールしてみましょう。

Ubuntu Linuxの場合は、aptでインストールすることができます。パッケージ名は「stunnel4」です。

$ sudo apt install stunnel4

インストールされるコマンドは「stunnel4」とそのエイリアス、そして「stunnel3」です。

$ ll /usr/bin/stunnel*
lrwxrwxrwx 1 root root      8 Apr 19  2018 /usr/bin/stunnel -> stunnel4*
-rwxr-xr-x 1 root root   2788 Apr 19  2018 /usr/bin/stunnel3*
-rwxr-xr-x 1 root root 175888 Apr 19  2018 /usr/bin/stunnel4*

「stunnel4」のバージョンを見ると、4という名前の割には5.44がインストールされたようです。最新版ではありませんが、今回はこれで
良いでしょう。

$ stunnel4 -version
stunnel 5.44 on x86_64-pc-linux-gnu platform
Compiled with OpenSSL 1.1.0g  2 Nov 2017
Running  with OpenSSL 1.1.1  11 Sep 2018
Update OpenSSL shared libraries or rebuild stunnel
Threading:PTHREAD Sockets:POLL,IPv6,SYSTEMD TLS:ENGINE,FIPS,OCSP,PSK,SNI Auth:LIBWRAP
 
Global options:
pid                    = /var/run/stunnel4.pid
RNDbytes               = 64
RNDfile                = /dev/urandom
RNDoverwrite           = yes
 
Service-level options:
ciphers                = FIPS (with "fips = yes")
ciphers                = HIGH:!DH:!aNULL:!SSLv2 (with "fips = no")
curve                  = prime256v1
debug                  = daemon.notice
logId                  = sequential
options                = NO_SSLv2
options                = NO_SSLv3
sessionCacheSize       = 1000
sessionCacheTimeout    = 300 seconds
stack                  = 65536 bytes
TIMEOUTbusy            = 300 seconds
TIMEOUTclose           = 60 seconds
TIMEOUTconnect         = 10 seconds
TIMEOUTidle            = 43200 seconds
verify                 = none

/etc/stunnel/READMEを見ると、ENABLEDを設定してね、サンプルがあるよ、と書かれています。

Stunnel 4 configuration files.

Files found under the /etc/stunnel directory that end with .conf are
used by the stunnel4 service as configuration files, and each will be
used to start a daemon process setting up a tunnel with the given
configuration. Note that this directory is initially empty, as the
settings you may want for your tunnels are completely system dependent.

In order to have the tunnels start up automatically on system boot you
must *also* set ENABLED to 1 in /etc/default/stunnel4

A sample configuration file with defaults may be found at
 /usr/share/doc/stunnel4/examples/stunnel.conf-sample

サンプルの内容を見てみます。

$ zcat /usr/share/doc/stunnel4/examples/stunnel.conf-sample.gz
; Sample stunnel configuration file for Unix by Michal Trojnara 2002-2017
; Some options used here may be inadequate for your particular configuration
; This sample file does *not* represent stunnel.conf defaults
; Please consult the manual for detailed description of available options

; **************************************************************************
; * Global options                                                         *
; **************************************************************************

; It is recommended to drop root privileges if stunnel is started by root
;setuid = stunnel4
;setgid = stunnel4

; PID file is created inside the chroot jail (if enabled)
;pid = /var/run/stunnel.pid

; Debugging stuff (may be useful for troubleshooting)
;foreground = yes
;debug = info
;output = /var/log/stunnel.log

; Enable FIPS 140-2 mode if needed for compliance
;fips = yes

; The pkcs11 engine allows for authentication with cryptographic
; keys isolated in a hardware or software token
; MODULE_PATH specifies the path to the pkcs11 module shared library,
; e.g. softhsm2.dll or opensc-pkcs11.so
; Each section using this feature also needs the "engineId = pkcs11" option
;engine = pkcs11
;engineCtrl = MODULE_PATH:/usr/lib/softhsm/libsofthsm2.so
;engineCtrl = PIN:1234

; **************************************************************************
; * Service defaults may also be specified in individual service sections  *
; **************************************************************************

; Enable support for the insecure SSLv3 protocol
;options = -NO_SSLv3

; These options provide additional security at some performance degradation
;options = SINGLE_ECDH_USE
;options = SINGLE_DH_USE

; **************************************************************************
; * Include all configuration file fragments from the specified folder     *
; **************************************************************************

;include = /etc/stunnel/conf.d

; **************************************************************************
; * Service definitions (remove all services for inetd mode)               *
; **************************************************************************

; ***************************************** Example TLS client mode services

; The following examples use /etc/ssl/certs, which is the common location
; of a hashed directory containing trusted CA certificates.  This is not
; a hardcoded path of the stunnel package, as it is not related to the
; stunnel configuration in /etc/stunnel/.

[gmail-pop3]
client = yes
accept = 127.0.0.1:110
connect = pop.gmail.com:995
verifyChain = yes
CApath = @sysconfdir/ssl/certs
checkHost = pop.gmail.com
OCSPaia = yes

[gmail-imap]
client = yes
accept = 127.0.0.1:143
connect = imap.gmail.com:993
verifyChain = yes
CApath = @sysconfdir/ssl/certs
checkHost = imap.gmail.com
OCSPaia = yes

[gmail-smtp]
client = yes
accept = 127.0.0.1:25
connect = smtp.gmail.com:465
verifyChain = yes
CApath = @sysconfdir/ssl/certs
checkHost = smtp.gmail.com
OCSPaia = yes

; Encrypted HTTP proxy authenticated with a client certificate
; located in a cryptographic token
;[example-pkcs11]
;client = yes
;accept = 127.0.0.1:8080
;connect = example.com:8443
;engineId = pkcs11
;cert = pkcs11:token=MyToken;object=MyCert
;key = pkcs11:token=MyToken;object=MyKey

; ***************************************** Example TLS server mode services

;[pop3s]
;accept  = 995
;connect = 110
;cert = /etc/stunnel/stunnel.pem

;[imaps]
;accept  = 993
;connect = 143
;cert = /etc/stunnel/stunnel.pem

;[ssmtp]
;accept  = 465
;connect = 25
;cert = /etc/stunnel/stunnel.pem

; TLS front-end to a web server
;[https]
;accept  = 443
;connect = 80
;cert = /etc/stunnel/stunnel.pem
; "TIMEOUTclose = 0" is a workaround for a design flaw in Microsoft SChannel
; Microsoft implementations do not use TLS close-notify alert and thus they
; are vulnerable to truncation attacks
;TIMEOUTclose = 0

; Remote shell protected with PSK-authenticated TLS
; Create "/etc/stunnel/secrets.txt" containing IDENTITY:KEY pairs
;[shell]
;accept = 1337
;exec = /bin/sh
;execArgs = sh -i
;ciphers = PSK
;PSKsecrets = /etc/stunnel/secrets.txt

; Non-standard MySQL-over-TLS encapsulation connecting the Unix socket
;[mysql]
;cert = /etc/stunnel/stunnel.pem
;accept = 3307
;connect = /run/mysqld/mysqld.sock

; vim:ft=dosini

ドキュメントもExampleを見てもよいですし、こちらも参考になりそうですね。

stunnelの設定を行う。

最初にstunnel4サービスの状態を確認しています。

$ sudo systemctl status stunnel4
● stunnel4.service - LSB: Start or stop stunnel 4.x (TLS tunnel for network daemons)
   Loaded: loaded (/etc/init.d/stunnel4; generated)
   Active: active (exited) since Tue 2020-04-07 14:59:47 UTC; 6min ago
     Docs: man:systemd-sysv-generator(8)
    Tasks: 0 (limit: 2317)
   CGroup: /system.slice/stunnel4.service

Apr 07 14:59:47 ubuntu1804.localdomain systemd[1]: Starting LSB: Start or stop stunnel 4.x (TLS tunnel for network daemons)...
Apr 07 14:59:47 ubuntu1804.localdomain stunnel4[1931]: TLS tunnels disabled, see /etc/default/stunnel4
Apr 07 14:59:47 ubuntu1804.localdomain systemd[1]: Started LSB: Start or stop stunnel 4.x (TLS tunnel for network daemons).

よくよく見ると、init.dですね。

   Loaded: loaded (/etc/init.d/stunnel4; generated)

インストール直後は停止しています。

ここで、環境変数の設定を行います。
/etc/default/stunnel4

# /etc/default/stunnel
# Julien LEMOINE <speedblue@debian.org>
# September 2003

# Change to one to enable stunnel automatic startup
ENABLED=0
FILES="/etc/stunnel/*.conf"
OPTIONS=""

# Change to one to enable ppp restart scripts
PPP_RESTART=0

# Change to enable the setting of limits on the stunnel instances
# For example, to set a large limit on file descriptors (to enable
# more simultaneous client connections), set RLIMITS="-n 4096"
# More than one resource limit may be modified at the same time,
# e.g. RLIMITS="-n 4096 -d unlimited"
RLIMITS=""

「ENABLED」を1に変更。

#ENABLED=0
ENABLED=1

起動。「restart」にしましたけど。

$ sudo systemctl restart stunnel4

これで、stunnelが利用できるようになります。

では、設定ファイルを書いていきましょう。Exampleを参考にします。

あと、こちらも参考にしましょう。

stunnel TLS Proxy

SSLTLSなクライアントを、SSLTLSを使用するサーバーに対応させる

お題として、https://www.google.com/へのHTTPSアクセスをstunnelに肩代わりしてもらい、クライアントからはHTTPでアクセスするように
してみましょう。

stunnelは「/etc/stunnel/*.conf」というルールで設定ファイルを読み込むようになっているので、ミニマムとして以下の設定を記述。
※今回は、グローバルな設定はパスします
/etc/stunnel/stunnel.conf

[google-web]
client = yes
accept = 127.0.0.1:80
connect = www.google.com:443
verifyChain = yes
CApath = /etc/ssl/certs
checkHost = www.google.com
OCSPaia = yes

それぞれ、以下の意味になります。

  • client … yesの場合、クライアントに対してSSLTLS通信を肩代わりする
  • accept … リッスンするアドレス、ポート
  • connect … 接続先のアドレス、ポート
  • verifyChain … ルートCAから始まる証明書チェーンを検証する
  • CApath … verifyChainまたはverifyPeerオプションが使用する証明書が格納されたディレクト
  • checkHost … 証明書検証時のホスト名
  • OCSPaia … AIA OCSPレスポンダーを使用する

今回は、実はclient、accept、connectの3つだけで動作するのですが。

acceptは、バインド先のIPアドレスを指定できます。やるかどうかはさておき、「0.0.0.0」で全ネットワークインターフェースにも
バインド可能です。

accept = 127.0.0.1:80
;accept = 0.0.0.0:80

ところで、Exampleなどにある「@sysconfdir」というのは、今回の環境では以下のディレクトリを指しています。

/etc/ssl/certs

では、stunnelを再起動。

$ sudo systemctl restart stunnel4

起動状態を確認。

$ sudo systemctl status stunnel4
● stunnel4.service - LSB: Start or stop stunnel 4.x (TLS tunnel for network daemons)
   Loaded: loaded (/etc/init.d/stunnel4; generated)
   Active: active (running) since Tue 2020-04-07 15:19:17 UTC; 2s ago
     Docs: man:systemd-sysv-generator(8)
  Process: 2624 ExecStop=/etc/init.d/stunnel4 stop (code=exited, status=0/SUCCESS)
  Process: 2646 ExecStart=/etc/init.d/stunnel4 start (code=exited, status=0/SUCCESS)
    Tasks: 2 (limit: 2317)
   CGroup: /system.slice/stunnel4.service
           └─2673 /usr/bin/stunnel4 /etc/stunnel/stunnel.conf

Apr 07 15:19:17 ubuntu1804.localdomain systemd[1]: Starting LSB: Start or stop stunnel 4.x (TLS tunnel for network daemons)...
Apr 07 15:19:17 ubuntu1804.localdomain systemd[1]: Started LSB: Start or stop stunnel 4.x (TLS tunnel for network daemons).
Apr 07 15:19:17 ubuntu1804.localdomain stunnel4[2646]: Starting TLS tunnels: /etc/stunnel/stunnel.conf: started (no pid=pidfile specified!)

telnetで確認してみましょう。

$ curl telnet://localhost:80
GET / HTTP/1.1
Host: www.google.com


HTTP/1.1 200 OK
Date: Wed, 08 Apr 2020 14:51:51 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=ISO-8859-1
P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."
Server: gws
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
Set-Cookie: 1P_JAR=2020-04-08-14; expires=Fri, 08-May-2020 14:51:51 GMT; path=/; domain=.google.com; Secure
Set-Cookie: NID=201=K1ffuoUljb0TF4f0eDIAqPm5Cdym1Do5K_AAlqUi69BSW6rf_QmRQIneW6vIMX03PYX3bEnDCBnN6_nciHcgHfNJBSBAmynzVhNzy2nzCJ2HpNOHMliECZmDmNueQiB_mGFEi7pg6C02nQen3vJMmny4vLxRvNAsvCa9R4CBZpI; expires=Thu, 08-Oct-2020 14:51:51 GMT; path=/; domain=.google.com; HttpOnly
Alt-Svc: quic=":443"; ma=2592000; v="46,43",h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,h3-T050=":443"; ma=2592000
Accept-Ranges: none
Vary: Accept-Encoding
Transfer-Encoding: chunked

64e4
<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="ja"><head><meta content="&#19990;&#30028;&#20013;&#12398;&#12354;&#12425;&#12422;&#12427;&#24773;&#22577;&#12434;&#26908;&#32034;&#12377;&#12427;&#12383;&#12417;&#12398;&#12484;&#12540;&#12523;&#12434;&#25552;&#20379;&#12375;&#12390;&#12356;&#12414;&#12377;&#12290;&#12373;&#12414;&#12374;&#12414;&#12394;&#26908;&#32034;&#27231;&#33021;&#12434;&#27963;&#29992;&#12375;&#12390;&#12289;&#12362;&#25506;&#12375;&#12398;&#24773;&#22577;&#12434;&#35211;&#12388;&#12369;&#12390;&#12367;&#12384;&#12373;&#12356;&#12290;" name="description"><meta content="noodp" name="robots"><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"><meta content="/logos/doodles/2020/thank-you-emergency-services-workers-6753651837108755-law.gif" itemprop="image"><meta content="&#25937;&#24613;&#38538;&#21729;&#12398;&#12415;&#12394;&#12373;&#12435;&#12289;&#12354;&#12426;&#12364;&#12392;&#12358;&#12290;" property="twitter:title"><meta content="&#25937;&#24613;&#38538;&#21729;&#12398;&#12415;&#12394;&#12373;&#12435;&#12289;&#12354;&#12426;&#12364;&#12392;&#12358;&#12290;#GoogleDoodle" property="twitter:description"><meta content="&#25937;&#24613;&#38538;&#21729;&#12398;&#12415;&#12394;&#12373;&#12435;&#12289;&#12354;&#12426;&#12364;&#12392;&#12358;&#12290;#GoogleDoodle" property="og:description"><meta content="summary_large_image" property="twitter:card"><meta content="@GoogleDoodles" property="twitter:site"><meta content="https://www.google.com/logos/doodles/2020/thank-you-emergency-services-workers-6753651837108755-2xa.gif" property="twitter:image"><meta content="https://www.google.com/logos/doodles/2020/thank-you-emergency-services-workers-6753651837108755-2xa.gif" property="og:image"><meta content="1288" property="og:image:width"><meta content="460" property="og:image:height"><meta content="https://www.google.com/logos/doodles/2020/thank-you-emergency-services-workers-6753651837108755-2xa.gif" property="og:url"><meta content="video.other" property="og:type"><title>Google</title><script nonce="FqdOdIcTeadLpYiSohseeQ==">(function()

〜省略〜

function _F_installCss(c){}
(function(){google.spjs=false;google.snet=true;google.em=[];google.emw=false;google.pdt=0;google.uwp=false;})();(function(){var pmc='{\x22d\x22:{},\x22sb_he\x22:{\x22agen\x22:true,\x22cgen\x22:true,\x22client\x22:\x22heirloom-hp\x22,\x22dh\x22:true,\x22dhqt\x22:true,\x22ds\x22:\x22\x22,\x22ffql\x22:\x22ja\x22,\x22fl\x22:true,\x22host\x22:\x22google.com\x22,\x22isbh\x22:28,\x22jsonp\x22:true,\x22msgs\x22:{\x22cibl\x22:\x22&#26908;&#32034;&#12434;&#12463;&#12522;&#12450;\x22,\x22dym\x22:\x22&#12418;&#12375;&#12363;&#12375;&#12390;:\x22,\x22lcky\x22:\x22I\\u0026#39;m Feeling Lucky\x22,\x22lml\x22:\x22&#35443;&#32048;\x22,\x22oskt\x22:\x22&#20837;&#21147;&#12484;&#12540;&#12523;\x22,\x22psrc\x22:\x22&#12371;&#12398;&#26908;&#32034;&#12461;&#12540;&#12527;&#12540;&#12489;&#12399;\\u003Ca href\x3d\\\x22/history\\\x22\\u003E&#12454;&#12455;&#12502;&#23653;&#27508;\\u003C/a\\u003E&#12363;&#12425;&#21066;&#38500;&#12373;&#12428;&#12414;&#12375;&#12383;\x22,\x22psrl\x22:\x22&#21066;&#38500;\x22,\x22sbit\x22:\x22&#30011;&#20687;&#12391;&#26908;&#32034;\x22,\x22srch\x22:\x22Google &#26908;&#32034;\x22},\x22ovr\x22:{},\x22pq\x22:\x22\x22,\x22refpd\x22:true,\x22refspre\x22:true,\x22rfs\x22:[],\x22sbpl\x22:16,\x22sbpr\x22:16,\x22scd\x22:10,\x22stok\x22:\x224H1gy_x0YYHGa1RJ1NO8X9IIrZs\x22,\x22uhde\x22:false}}';google.pmc=JSON.parse(pmc);})();</script>        </body></html>
0

HTTPでアクセスしましたが、結果が返ってきました。

$ curl telnet://localhost:80
GET / HTTP/1.1
Host: www.google.com

tcpdumpで見ると、ちゃんと443で通信しに行っていることが確認できます。

$ sudo tcpdump -i any -n tcp port 443
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
14:54:19.139300 IP 192.168.121.216.38072 > 216.58.221.228.443: Flags [S], seq 1433332297, win 64240, options [mss 1460,sackOK,TS val 4078539507 ecr 0,nop,wscale 7], length 0
14:54:19.198305 IP 216.58.221.228.443 > 192.168.121.216.38072: Flags [S.], seq 929050666, ack 1433332298, win 60192, options [mss 1380,sackOK,TS val 1149326253 ecr 4078539507,nop,wscale 8], length 0
14:54:19.198371 IP 192.168.121.216.38072 > 216.58.221.228.443: Flags [.], ack 1, win 502, options [nop,nop,TS val 4078539566 ecr 1149326253], length 0
14:54:19.198853 IP 192.168.121.216.38072 > 216.58.221.228.443: Flags [P.], seq 1:347, ack 1, win 502, options [nop,nop,TS val 4078539567 ecr 1149326253], length 346
14:54:19.256534 IP 216.58.221.228.443 > 192.168.121.216.38072: Flags [.], ack 347, win 240, options [nop,nop,TS val 1149326312 ecr 4078539567], length 0
14:54:19.283249 IP 216.58.221.228.443 > 192.168.121.216.38072: Flags [P.], seq 1:2633, ack 347, win 240, options [nop,nop,TS val 1149326338 ecr 4078539567], length 2632
14:54:19.286097 IP 192.168.121.216.38072 > 216.58.221.228.443: Flags [.], ack 2633, win 495, options [nop,nop,TS val 4078539651 ecr 1149326338], length 0

ssでも見ておきましょう。

$ sudo ss -tnp | grep 443
ESTAB  0        0            192.168.121.216:38112       216.58.221.228:443      users:(("stunnel4",pid=3116,fd=10)) 

大丈夫そうですね。

SSLTLSなサーバーを、SSLTLSに対応させる

次に、非SSLTLSなサーバーを、SSLTLS信越しに使えるようにしてみましょう。

Pythonで、簡易なHTTPサーバーを立てます。

$ echo 'Hello World!!' > hello.txt
$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

確認。

$ curl localhost:8000/hello.txt
Hello World!!

このHTTPサーバーを、stunnelを使ってSSLTLSに対応させてみます。

証明書が必要になるので、今回は自己署名証明書を作成しましょう。

秘密鍵の作成。

$ openssl genrsa -aes256 -out sample.key 2048

パスワードの解除。

$ openssl rsa -in sample.key -out sample.key

CSRの作成。

$ openssl req -new -key sample.key -out sample.csr

署名。

$ openssl x509 -req -days 365 -in sample.csr -signkey sample.key -out sample.crt

では、このキーを使うように、/etc/stunnel/stunnel.confを修正します。

[python-http]
accept  = 8443
connect = 8000
cert = /path/to/sample.crt
key = /path/to/sample.key

accept、connectの意味は、クライアントとして動作する時と同じですね。今回はポートのみ指定で、バックエンドのPython HTTPサーバーの
前段に8443ポートで構えます。

cert、keyは自己署名証明書秘密鍵をそれぞれ指定します。

stunnelを再起動。

$ sudo systemctl restart stunnel4

自己署名証明書なので、curlの「--cacert」オプションに指定して実行。

$ curl --cacert sample.crt https://localhost:8443/hello.txt
Hello World!!

OKですね。

これで、stunnelを使ったSSLTLSトンネリングの初歩的な内容が確認できました、と。

Jib Maven Pluginで、Dockerコンテナイメージを作る

これは、なにをしたくて書いたもの?

以前、Jib Coreを使ってDockerイメージを作ってみました。

Jib Coreで、Dockerコンテナイメージを作ってみる - CLOVER🍀

これでJibの基本的なところはわかった…ことにして、今度はJib Maven Pluginを使ってDockerイメージを作ってみたいと思います。

Jib Maven Plugin

文字通り、Jibの機能が使えるMaven Pluginで、DockerまたはOCIイメージを作ることができます。

https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin

作成したイメージは、コンテナレジストリにアップロードしたり、Dockerデーモンに送ったり、tarファイルにしたりできます。

それでは、使っていってみましょう。

環境

今回の環境は、こちらです。

$ java --version
openjdk 11.0.6 2020-01-14
OpenJDK Runtime Environment (build 11.0.6+10-post-Ubuntu-1ubuntu118.04.1)
OpenJDK 64-Bit Server VM (build 11.0.6+10-post-Ubuntu-1ubuntu118.04.1, mixed mode, sharing)


$ mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.6, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "4.18.0-25-generic", arch: "amd64", family: "unix"

また、Jib Maven Pluginは2.1.0を使用します。

サンプルアプリケーション

先に、お題となるサンプルアプリケーションが必要ですね。

こちらで作ったものを流用します。

Jib Coreで、Dockerコンテナイメージを作ってみる - CLOVER🍀

pom。RESTEasyは、最新版にしておきました。
pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.littlewings</groupId>
    <artifactId>jib-simple-jaxrs-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jdk-http</artifactId>
            <version>4.5.3.Final</version>
        </dependency>
    </dependencies>
</project>

起動クラス+JAX-RSリソースクラス。
src/main/java/org/littlewings/jaxrs/Server.java

package org.littlewings.jaxrs;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

import com.sun.net.httpserver.HttpServer;
import org.jboss.logging.Logger;
import org.jboss.resteasy.plugins.server.sun.http.HttpContextBuilder;

public class Server {
    public static void main(String... args) throws IOException {
        Logger logger = Logger.getLogger(Server.class);

        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 10);

        try {
            HttpContextBuilder builder = new HttpContextBuilder();
            builder.getDeployment().getActualResourceClasses().add(HelloResource.class);

            builder.bind(server);

            server.start();

            logger.info("server start.");

            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(1L);
                } catch (InterruptedException e) {
                    // ignore
                }
            }
        } finally {
            server.stop(0);
            logger.info("server stop.");
        }
    }

    @Path("hello")
    public static class HelloResource {
        @GET
        @Produces(MediaType.TEXT_PLAIN)
        public String message(@QueryParam("value") String value) {
            return "Hello " + Optional.ofNullable(value).orElse("World") + "!!";
        }
    }
}

こちらを、Jibを使ってDockerイメージにしていきましょう。

Jib Maven Pluginを組み込む

まずは、なにも考えずにプラグインを組み込んでみます。

    <build>
        <plugins>
            <plugin>
                <groupId>com.google.cloud.tools</groupId>
                <artifactId>jib-maven-plugin</artifactId>
                <version>2.1.0</version>
                <configuration>
                    <to>
                        <image>kazuhira/jib-simple-jaxrs-server</image>
                    </to>
                </configuration>
            </plugin>
        </plugins>
    </build>

「jib:dockerBuild」でDockerイメージを作成しましょう。

$ mvn compile jib:dockerBuild

どうやら、「gcr.io/distroless/java:11」をベースにしてイメージを作ったみたいです。

[INFO] Containerizing application to Docker daemon as kazuhira/jib-simple-jaxrs-server...
[WARNING] Base image 'gcr.io/distroless/java:11' does not use a specific image digest - build may not be reproducible
[INFO] Using base image with digest: sha256:c94feda039172152495b5cd60a350a03162fce4f8986b560ea555de4d276ce19
[INFO] 
[INFO] Container entrypoint set to [java, -cp, /app/resources:/app/classes:/app/libs/*, org.littlewings.jaxrs.Server]
[INFO] 
[INFO] Built image to Docker daemon as kazuhira/jib-simple-jaxrs-server
[INFO] Executing tasks:
[INFO] [==============================] 100.0% complete
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  8.328 s
[INFO] Finished at: 2020-04-05T19:43:05+09:00
[INFO] ------------------------------------------------------------------------

確認。50年前のタイムスタンプのDockerイメージができました。

$ docker image ls | grep simple
kazuhira/jib-simple-jaxrs-server   latest              cf8664e3692c        50 years ago        199MB

Dockerイメージの方も確認してみましょう。

$ docker container run -it --rm -p 8080:8080 kazuhira/jib-simple-jaxrs-server:latest 
Apr 05, 2020 10:50:26 AM org.littlewings.jaxrs.Server main
INFO: server start.

OKですね。

$ curl localhost:8080/hello
Hello World!!

では、少しずつ設定を変えていってみましょう。Jib Coreの時に習う感じします。

あと、Exampleも参考にするとよいでしょう。

Example

タグを指定する

これは、単純にイメージ名の隣に「:」で区切ってタグを指定すればOKです。

            <plugin>
                <groupId>com.google.cloud.tools</groupId>
                <artifactId>jib-maven-plugin</artifactId>
                <version>2.1.0</version>
                <configuration>
                    <to>
                        <image>kazuhira/jib-simple-jaxrs-server:0.0.1</image>
                    </to>
                </configuration>
            </plugin>

ビルド後。

$ docker image ls | grep simple
kazuhira/jib-simple-jaxrs-server   0.0.1               cf8664e3692c        50 years ago        199MB
ベースイメージを変更する

最初はデフォルトで使われるベースイメージ(Distroless)を使いましたが、ベースイメージを変更してみましょう。

            <plugin>
                <groupId>com.google.cloud.tools</groupId>
                <artifactId>jib-maven-plugin</artifactId>
                <version>2.1.0</version>
                <configuration>
                    <from>
                        <image>adoptopenjdk:11-jre-hotspot</image>
                    </from>
                    <to>
                        <image>kazuhira/jib-simple-jaxrs-server:0.0.1</image>
                    </to>
                </configuration>
            </plugin>

from、imageで指定します。今回はAdoptOpenJDKにしました。

Extended Usage

ところで、README.mdを見ていると、なにも指定がない場合のベースイメージは「gcr.io/distroless/java」が使われてると書かれて
いた割には最初にダウンロードされたのは「gcr.io/distroless/java:11」でした。

Setting the Base Image

どうなっているんでしょう?

どうやら、Javaのバージョンを見てベースイメージを決めているようですね。

https://github.com/GoogleContainerTools/jib/blob/v2.1.0-maven/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessor.java#L577-L592

Java VMに渡すオプションを設定する

Jib Maven Pluginを使った場合は、ENTRYPOINTを直接指定することは少ないと思います。

代わりに、jvmFlagやmainClassなどを使用します。

entrypointという設定項目自体はあるのですが、jvmFlagsなどを設定しても無視されるそうな。

The command to start the container with (similar to Docker's ENTRYPOINT instruction). If set, then jvmFlags and mainClass are ignored. You may also set INHERIT to indicate that the entrypoint and args should be inherited from the base image.*

では、jvmFlagを使ってみましょう。「-Xmx」を指定してみます。

            <plugin>
                <groupId>com.google.cloud.tools</groupId>
                <artifactId>jib-maven-plugin</artifactId>
                <version>2.1.0</version>
                <configuration>
                    <from>
                        <image>adoptopenjdk:11-jre-hotspot</image>
                    </from>
                    <to>
                        <image>kazuhira/jib-simple-jaxrs-server:0.0.1</image>
                    </to>
                    <container>
                        <jvmFlags>
                          <jvmFlag>-Xmx256m</jvmFlag>
                        </jvmFlags>
                    </container>
                </configuration>
            </plugin>

指定する時は、container配下にするようですね。

起動後にコンテナ内に入って確認してみると、Java VMへの引数が追加されていることが確認できますね。

$ docker container exec -it [コンテナ名] bash
root@5de3f4e084f2:/# ps -ef | grep java
root         1     0 27 11:20 pts/0    00:00:01 java -Xmx256m -cp /app/resources:/app/classes:/app/libs/* org.littlewings.jaxrs.Server
root        36    26  0 11:20 pts/1    00:00:00 grep --color=auto java

https://github.com/GoogleContainerTools/jib/blob/v2.1.0-maven/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessor.java#L558-L564

なお、実行時にJava VMへの引数を指定したい場合は、JAVA_TOOL_OPTIONS環境変数を使うようです。
※ベースイメージがdistroless/javaの時に限るとは書いていますが…これはJavaのドキュメントにも記載のある環境変数ですが…

https://github.com/GoogleContainerTools/jib/blob/v2.1.0-maven/docs/faq.md#how-do-i-set-parameters-for-my-image-at-runtime

mainClassについては自動で検出するようですが、自分で指定することも可能です。

https://github.com/GoogleContainerTools/jib/blob/v2.1.0-maven/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessor.java#L554-L556

あと、プログラム自体の起動引数についてはargsで指定します。

ポートをEXPOSEする

ポートをEXPOSEするには、ports、portで指定します。

            <plugin>
                <groupId>com.google.cloud.tools</groupId>
                <artifactId>jib-maven-plugin</artifactId>
                <version>2.1.0</version>
                <configuration>
                    <from>
                        <image>adoptopenjdk:11-jre-hotspot</image>
                    </from>
                    <to>
                        <image>kazuhira/jib-simple-jaxrs-server:0.0.1</image>
                    </to>
                    <container>
                        <jvmFlags>
                          <jvmFlag>-Xmx256m</jvmFlag>
                        </jvmFlags>
                        <ports>
                            <port>8080</port>
                        </ports>
                    </container>
                </configuration>
            </plugin>

確認。

$ docker container ps
CONTAINER ID        IMAGE                                    COMMAND                  CREATED             STATUS              PORTS               NAMES
bd8aa0836e4d        kazuhira/jib-simple-jaxrs-server:0.0.1   "java -Xmx256m -cp /…"   5 seconds ago       Up 2 seconds        8080/tcp            busy_carson
イメージの作成時刻を変更する

作成時刻が50年前になっているのを変更するには、creationTimeを使用します。

            <plugin>
                <groupId>com.google.cloud.tools</groupId>
                <artifactId>jib-maven-plugin</artifactId>
                <version>2.1.0</version>
                <configuration>
                    <from>
                        <image>adoptopenjdk:11-jre-hotspot</image>
                    </from>
                    <to>
                        <image>kazuhira/jib-simple-jaxrs-server:0.0.1</image>
                    </to>
                    <container>
                        <jvmFlags>
                          <jvmFlag>-Xmx256m</jvmFlag>
                        </jvmFlags>
                        <creationTime>USE_CURRENT_TIMESTAMP</creationTime>
                        <ports>
                            <port>8080</port>
                        </ports>
                    </container>
                </configuration>
            </plugin>

「USE_CURRENT_TIMESTAMP」を使用すると、現在時刻でイメージが作成されます。

$ docker image ls | grep simple
kazuhira/jib-simple-jaxrs-server   0.0.1               36ac8b04db85        2 minutes ago       228MB

ただし、現在時刻を使ってしまうと再現性を失うことになる点に注意してください。

Why is my image created 48+ years ago?

また、ISO 8601フォーマットで指定することもできます。

            <plugin>
                <groupId>com.google.cloud.tools</groupId>
                <artifactId>jib-maven-plugin</artifactId>
                <version>2.1.0</version>
                <configuration>
                    <from>
                        <image>adoptopenjdk:11-jre-hotspot</image>
                    </from>
                    <to>
                        <image>kazuhira/jib-simple-jaxrs-server:0.0.1</image>
                    </to>
                    <container>
                        <jvmFlags>
                          <jvmFlag>-Xmx256m</jvmFlag>
                        </jvmFlags>
                        <creationTime>2020-01-15T10:30:45+09:00</creationTime>
                        <ports>
                            <port>8080</port>
                        </ports>
                    </container>
                </configuration>
            </plugin>

実体としては、DateTimeFormatter.ISO_DATE_TIMEでパースできる必要があります。

結果。

$ docker image ls | grep simple
kazuhira/jib-simple-jaxrs-server   0.0.1               bf6479b69280        2 months ago        228MB
ビルドプロセスに組み込む

Mavenプラグインとして使っているのですし、package時にDockerイメージを作って欲しいものです。

以下を参考に、Mavenのビルドプロセスに組み込んでみましょう。

Bind to a lifecycle

Profile「docker」にして、通常のパッケージングとは別にしてみました。

    <profiles>
        <profile>
            <id>docker</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>com.google.cloud.tools</groupId>
                        <artifactId>jib-maven-plugin</artifactId>
                        <version>2.1.0</version>
                        <executions>
                            <execution>
                                <phase>package</phase>
                                <goals>
                                    <goal>dockerBuild</goal>
                                </goals>
                            </execution>
                        </executions>
                        <configuration>
                            <from>
                                <image>adoptopenjdk:11-jre-hotspot</image>
                            </from>
                            <to>
                                <image>kazuhira/jib-simple-jaxrs-server:0.0.1</image>
                            </to>
                            <container>
                                <jvmFlags>
                                    <jvmFlag>-Xmx256m</jvmFlag>
                                </jvmFlags>
                                <creationTime>2020-01-15T10:30:45+09:00</creationTime>
                                <ports>
                                    <port>8080</port>
                                </ports>
                            </container>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

「package」ゴール実行時に、「dockerBuild」するようにしてあります。

                        <executions>
                            <execution>
                                <phase>package</phase>
                                <goals>
                                    <goal>dockerBuild</goal>
                                </goals>
                            </execution>
                        </executions>

あとは、Profileを指定してビルドしましょう。

$ mvn -Pdocker package

こんなところでしょうか。

まとめ

Jib Maven Pluginを使って、Dockerイメージを作ってみました。

その他、WARに対して適用できたりしますし、設定も他にたくさんあるのですが、慣れればREADME.mdを見つつ使えそうな気がしますね。

WAR Projects

今回は、こんなところで。