IK.AM

@making's tech note


LegacyなJavaアプリをTanzu Application Platformにデプロイするまでのメモ

🗃 {Dev/CaaS/Kubernetes/TAP}
🏷 Kubernetes 🏷 Cartographer 🏷 Java 🏷 Tanzu 🏷 TAP 🏷 Service Binding 
🗓 Updated at 2022-06-02T13:32:57+09:00  🗓 Created at 2022-06-02T13:06:43+09:00 {✒️️ Edit  ⏰ History  🗑 Delete}

以下のStackを使用したレガシーなJavaアプリをTanzu Application Platform (TAP) 1.1にデプロイしてみます。

  • Apache Struts 1.2.x
  • iBatis 2.3.x
  • Spring Framework 3.2.x
  • Apache Ant

TAPのインストールはこちらを参照してください。

目次

ソースコードのダウンロード

レガシーなサンプルアプリはこちらからダウンロード

wget https://ja.osdn.net/projects/terasoluna/downloads/66350/terasoluna-server4jweb-toursample_2.0.6.2.zip
unzip terasoluna-server4jweb-toursample_2.0.6.2.zip
cd terasoluna-server4jweb-toursample_2.0.6.2
unzip toursample-javaweb.zip
cd toursample-javaweb

サンプルアプリのビルド

MavenやGradleを使用している場合は、Cloud Native Buildpacksを使用してTAP上でソースコードのビルドから行えますが、 Antはサポートされていないので、事前にwarをビルドして、warをTAPにデプロイするようにします。

TAPで利用できる再旧バージョンであるJava 8でビルドします。antも久々にインストールします。

brew install ant

ant/build.xmlを見るとTomcatがビルドに必要なため、Tomcatをダウンロードします。 サンプルでTomcat 6が使用されていましたが、ここではTomcat 9を使用します。 Tomcat 10以降ではServlet APIのpackage名が変わっているので使用できません。

wget https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.63/bin/apache-tomcat-9.0.63.zip
unzip apache-tomcat-9.0.63.zip -d /opt 

ant/build.propertieswebapsvr.homeをダウンロードしたディレクトリに変えます。またwarのファイル名をROOT.warになるように変更します。

cat <<'EOF' > ant/build.properties
# Keep
source.dir=./sources
web.inf.dir=./webapps/WEB-INF
lib.dir=./webapps/WEB-INF/lib
zip.dir=./terasoluna/src

## Change according to your tomcat env
webapsvr.home=/opt/apache-tomcat-9.0.63
webapsvr.lib.dir=${webapsvr.home}/lib
deploy.dir=${webapsvr.home}/webapps

## Change
context.name=ROOT
EOF

ログの出力先を標準出力にするためにsources/log4j.propertiesを次のように変更します。

cat <<EOF > sources/log4j.properties
# Keep
log4j.rootCategory=INFO, consoleLog, logfile
log4j.category.jp.terasoluna=INFO
log4j.category.org.springframework=INFO
log4j.category.org.apache.struts=INFO

log4j.appender.consoleLog=org.apache.log4j.ConsoleAppender
log4j.appender.consoleLog.Target = System.out
log4j.appender.consoleLog.layout = org.apache.log4j.PatternLayout
log4j.appender.consoleLog.layout.ConversionPattern=[%d{yyyy/MM/dd HH:mm:ss}][%p][%C{1}] %m%n

# Remove
# log4j.appender.logfile=...
EOF

これでantでビルドします。

ant -f ant/build.xml

ビルドできました。

Buildfile: /Users/toshiaki/blog/legacy/terasoluna-server4jweb-toursample_2.0.6.2/toursample-javaweb/ant/build.xml

clean:
   [delete] Deleting: /opt/apache-tomcat-9.0.63/webapps/ROOT.war

compile:
    [javac] /Users/toshiaki/blog/legacy/terasoluna-server4jweb-toursample_2.0.6.2/toursample-javaweb/ant/build.xml:69: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
    [javac] Compiling 97 source files to /Users/toshiaki/blog/legacy/terasoluna-server4jweb-toursample_2.0.6.2/toursample-javaweb/webapps/WEB-INF/classes
     [copy] Copying 10 files to /Users/toshiaki/blog/legacy/terasoluna-server4jweb-toursample_2.0.6.2/toursample-javaweb/webapps/WEB-INF/classes

native2ascii:
[native2ascii] Converting 16 files from /Users/toshiaki/blog/legacy/terasoluna-server4jweb-toursample_2.0.6.2/toursample-javaweb/sources to /Users/toshiaki/blog/legacy/terasoluna-server4jweb-toursample_2.0.6.2/toursample-javaweb/webapps/WEB-INF/classes

build:

deploy:
      [jar] Building jar: /Users/toshiaki/blog/legacy/terasoluna-server4jweb-toursample_2.0.6.2/toursample-javaweb/ROOT.war
     [copy] Copying 1 file to /opt/apache-tomcat-9.0.63/webapps
   [delete] Deleting: /Users/toshiaki/blog/legacy/terasoluna-server4jweb-toursample_2.0.6.2/toursample-javaweb/ROOT.war

BUILD SUCCESSFUL
Total time: 1 second

Flywayの導入

DBマイグレーションを手動で行いたくないのでFlywayを導入します。 と言ってもソースコードには手を入れたくないので、アプリの起動前にコマンドラインで実行するようにします。

wget https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline/5.2.4/flyway-commandline-5.2.4-linux-x64.tar.gz
tar xzvf flyway-commandline-5.2.4-linux-x64.tar.gz
mv flyway-5.2.4/ flyway

SQLの文字コードがSHIFT_JISX0213なのでUTF-8に変換しちゃいます。変換後のファイル名はFlywayの命名規則に合わせます。

iconv -f SHIFT_JISX0213 -t UTF-8 sqls/postgres/create_all_sequences.sql > flyway/sql/V1__create_all_sequences.sql
iconv -f SHIFT_JISX0213 -t UTF-8 sqls/postgres/create_all_tables.sql 	> flyway/sql/V2__create_all_tables.sql
iconv -f SHIFT_JISX0213 -t UTF-8 sqls/postgres/create_all_index.sql 	> flyway/sql/V3__create_all_index.sql
iconv -f SHIFT_JISX0213 -t UTF-8 sqls/postgres/insert_departure.sql 	> flyway/sql/V4__insert_departure.sql
iconv -f SHIFT_JISX0213 -t UTF-8 sqls/postgres/insert_arrival.sql 	> flyway/sql/V5__insert_arrival.sql
iconv -f SHIFT_JISX0213 -t UTF-8 sqls/postgres/insert_accommodation.sql > flyway/sql/V6__insert_accommodation.sql
iconv -f SHIFT_JISX0213 -t UTF-8 sqls/postgres/insert_age.sql 		> flyway/sql/V7__insert_age.sql
iconv -f SHIFT_JISX0213 -t UTF-8 sqls/postgres/insert_employee.sql 	> flyway/sql/V8__insert_employee.sql
iconv -f SHIFT_JISX0213 -t UTF-8 sqls/postgres/insert_customer.sql 	> flyway/sql/V9__insert_customer.sql
iconv -f SHIFT_JISX0213 -t UTF-8 sqls/postgres/insert_tourinfo.sql 	> flyway/sql/V10__insert_tourinfo.sql
iconv -f SHIFT_JISX0213 -t UTF-8 sqls/postgres/insert_tourcon.sql 	> flyway/sql/V11__insert_tourcon.sql
iconv -f SHIFT_JISX0213 -t UTF-8 sqls/postgres/insert_reserve.sql 	> flyway/sql/V12__insert_reserve.sql

Procfileの作成

Tomcat起動前にFlywayの実行や、META-INF/context.xmlに設定されているJNDIの設定を書き換えたいので、 Procfile を使用して、任意のスクリプトで起動できるようにします。

cat <<EOF > Procfile
web: bash /workspace/run.sh
EOF

起動スクリプトはrun.shに記述します。

データベース(PostgreSQL)への接続情報は Service Binding で渡されるものとします。 Well-known Secret Entries のフォーマットが使用されていることを前提にします。

cat <<'EOD' > run.sh
#!/bin/bash
set -e

BINDING_NAME=tour-db
DATABASE_HOST=$(cat ${SERVICE_BINDING_ROOT}/${BINDING_NAME}/host)
DATABASE_PORT=$(cat ${SERVICE_BINDING_ROOT}/${BINDING_NAME}/port)
DATABASE_USERNAME=$(cat ${SERVICE_BINDING_ROOT}/${BINDING_NAME}/username)
DATABASE_PASSWORD=$(cat ${SERVICE_BINDING_ROOT}/${BINDING_NAME}/password)
DATABASE_NAME=$(cat ${SERVICE_BINDING_ROOT}/${BINDING_NAME}/database)
DATABASE_URL="jdbc:postgresql://${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}"

cat << EOF > ./META-INF/context.xml
<Context>
  <Resource
     name="jdbc/terasolunaTourDataSource"
     type="javax.sql.DataSource"
     driverClassName="org.postgresql.Driver"
     password="${DATABASE_PASSWORD}"
     maxIdle="4"
     maxWait="5000"
     username="${DATABASE_USERNAME}"
     url="${DATABASE_URL}"
     maxActive="20"/>
</Context>
EOF

${JAVA_HOME}/bin/java \
  -cp `ls WEB-INF/lib/postgresql-*.jar`:./flyway/lib/community/* \
  org.flywaydb.commandline.Main migrate \
  -url=${DATABASE_URL} \
  -user=${DATABASE_USERNAME} \
  -password=${DATABASE_PASSWORD}

bash ${CATALINA_HOME}/bin/catalina.sh run
EOD
chmod +x run.sh

warの更新

ビルドしたwarにflywayやProcfileなどを詰め込みます。またソースコードにPostgreSQLのJDBCドライバーが含まれていないので、ドライバーもダウンロードして詰め込みます。

mkdir -P WEB-INF/lib
wget https://repo1.maven.org/maven2/org/postgresql/postgresql/42.3.6/postgresql-42.3.6.jar -P WEB-INF/lib
cp /opt/apache-tomcat-9.0.63/webapps/ROOT.war ./

次のコマンドで ROOT.war にファイルを追加します。

jar -uvf ROOT.war flyway/lib flyway/jars flyway/sql WEB-INF Procfile run.sh

pack CLIでコンテナイメージビルド

TAPにデプロイする前に pack CLIでコンテナイメージをビルドしてローカルで起動確認します。

pack build toursample --builder paketobuildpacks/builder:base --path ./ROOT.war -e BP_JVM_VERSION=8

ラップトップ上のDockerで起動

ビルドしたイメージをDockerで起動してみます。 その前にPostgreSQLを起動します。

docker run --rm \
 -p 5432:5432 \
 -e POSTGRES_DB=tour \
 -e POSTGRES_USER=tour \
 -e POSTGRES_PASSWORD=tour \
 bitnami/postgresql:14

Service Binding 用のディレクトリ・ファイルを作成します。

mkdir -p bindings/tour-db
echo postgresql > bindings/tour-db/type
echo host.docker.internal > bindings/tour-db/host
echo 5432 > bindings/tour-db/port
echo tour > bindings/tour-db/username
echo tour > bindings/tour-db/password
echo tour > bindings/tour-db/database

アプリを起動します。

docker run --rm \
  --name toursample \
  -p 8080:8080 \
  -v ${PWD}/bindings:/bindings \
  -e SERVICE_BINDING_ROOT=/bindings \
  -m 768m \
  toursample

http://localhost:8080 にアクセスするとトップ画面に遷移します。

image

会員ID 00000001, パスワード password でログインできます。

image

なぜかCtrl+Cでアプリが終了しないので、

docker kill toursample

を実行します。

TAPにデプロイ

いよいよTAPにデプロイします。

その前にPostgreSQLを用意します。ここではBitnamiのHelm Chartを使用します。

NAMESPACE=...
helm repo add bitnami https://charts.bitnami.com/bitnami
cat <<EOF > postgresql-values.yaml
resources:
  requests:
    cpu: 100m
    memory: 128Mi
auth:
  username: tour
  database: tour
  existingSecret: tour-db
persistence:
  size: 1Gi
volumePermissions:
  enabled: true
EOF
helm template -n ${NAMESPACE} tour-db bitnami/postgresql -f postgresql-values.yaml > tour-db.yaml

DBのパスワードのSecretはsecretgen controllerの Secret Template で生成します。 Secret Templateに設定するキーは、Helm Chartで必須のキー(postgres-password)とWell-known Secret Entries で期待されているキー(type, host, port, username, password, database)にします。

cat <<EOF > tour-db-secret.yaml
apiVersion: secretgen.k14s.io/v1alpha1
kind: Password
metadata:
  name: tour-db
  namespace: ${NAMESPACE}
spec:
  secretTemplate:
    type: servicebinding.io/postgresql
    stringData:
      type: postgresql
      host: tour-db-postgresql.${NAMESPACE}.svc.cluster.local
      port: "5432"
      username: tour
      password: \$(value)
      database: tour
      postgres-password: \$(value)
EOF

2つのYAMLをapplyします。

kubectl apply -f tour-db.yaml -f tour-db-secret.yaml

PostgreSQLのPodとSecretができていれば良いです。

$ kubectl get pod -n ${NAMESPACE} tour-db-postgresql-0 
NAME                   READY   STATUS    RESTARTS   AGE
tour-db-postgresql-0   1/1     Running   0          100s

$ kubectl get secret -n ${NAMESPACE} tour-db                                           
NAME      TYPE                           DATA   AGE
tour-db   servicebinding.io/postgresql   7      100s

ローカルファイルからTAPにデプロイします。pack CLIと同じようにwarファイルを直接渡したいのですが、 war/zipファイルのアップロード はApps Plugin for Tanzu CLI 0.7でサポートされる予定(TAP 1.2)です。

現在使用しているApps Pluginのバージョンは次のとおりです。

$ tanzu apps version
v0.5.1

このバージョンだとwarを一度展開する必要があります。次のようにunzipします。

unzip ROOT.war -d ROOT

いよいよデプロイします。なお、ソースコードはコンテナレジストリ(ghcr.io)にアップロードされるので事前にdocker loginしておく必要があります。

docker login ghcr.io -u ${GITHUB_USERNAME} -p ${GITHUB_API_TOKEN}

tanzu apps workload apply toursample \
  --app toursample \
  --local-path ./ROOT \
  --source-image ghcr.io/${GITHUB_USERNAME}/toursample-source \
  --type web \
  --build-env BP_JVM_VERSION=8 \
  --annotation autoscaling.knative.dev/minScale=1 \
  --service-ref tour-db=v1:Secret:tour-db \
  -n ${NAMESPACE} \
  -y

進捗は次のコマンドで確認できます。

tanzu apps workload tail -n ${NAMESPACE} toursample

しばらく(7-8分)するとデプロイが完了します。

ℹ️ 初回のデプロイ時にはKnativeのRevisionがなぜか00001と00002ができてしまいます。
00001はService Bindingがないrevisionでrace conditionのように見えます。
00001はしばらくすると消えるので、無視して0002以降を見てください。

$ tanzu apps workload get -n ${NAMESPACE} toursample
# toursample: Ready
---
lastTransitionTime: "2022-06-02T03:32:53Z"
message: ""
reason: Ready
status: "True"
type: Ready

Services
CLAIM     NAME      KIND     API VERSION
tour-db   tour-db   Secret   v1

Pods
NAME                                           STATUS      RESTARTS   AGE
toursample-00001-deployment-695d9b4985-tbpq2   Running     4          2m41s
toursample-00002-deployment-6b578796c-vmf5x    Running     0          2m40s
toursample-build-1-build-pod                   Succeeded   0          6m48s
toursample-config-writer-wcjbm-pod             Succeeded   0          3m37s

Knative Services
NAME         READY   URL
toursample   Ready   https://toursample-making.apps.jaguchi.maki.lol

URLにアクセスすれば、画面が表示されます。

image

ログインもできます。

image

(補足) Apps Plugin for Tanzu CLI 0.7を使用する

war/zipファイルから直接アップロードできるApps Plugin for Tanzu CLI 0.7の先行リリースが出たので試します。
https://github.com/vmware-tanzu/apps-cli-plugin/releases/tag/v0.7.0-build.1

wget https://github.com/vmware-tanzu/apps-cli-plugin/releases/download/v0.7.0-build.1/tanzu-apps-plugin-darwin-amd64-v0.7.0-build.1.tar.gz
tar -xvf tanzu-apps-plugin-darwin-amd64-v0.7.0-build.1.tar.gz
tanzu plugin install apps --local ./ --version v0.7.0-build.1

バージョンを確認します。

$ tanzu apps version
v0.7.0-build.1

これで次のようにwarからデプロイできました。

tanzu apps workload apply toursample \
  --app toursample \
  --local-path ./ROOT.war \
  --source-image ghcr.io/${GITHUB_USERNAME}/toursample-source \
  --type web \
  --build-env BP_JVM_VERSION=8 \
  --annotation autoscaling.knative.dev/minScale=1 \
  --service-ref tour-db=v1:Secret:tour-db \
  -n ${NAMESPACE} \
  -y

TAP 1.2で正式に対応される予定です。

アプリの削除

tanzu apps workload delete -n ${NAMESPACE} toursample -y
kubectl delete -f tour-db.yaml -f tour-db-secret.yaml
kubectl delete pvc data-blog-db-postgresql-0