Zero-Downtime Switchover With WAR and Parallel Deployment
Goal
You confirm how WAR deployment actually behaves, raise the version with parallel deployment without cutting sessions, and become able to write a deployment script that also verifies the result.
Why it matters
In a single-WAS environment, it is easy to think that zero-downtime deployment requires removing and adding servers
at an L4 in front. But Tomcat has a parallel deployment feature built in.
If you add ##버전 (a double hash plus a version) to the file name, several versions coexist under the same context,
and existing sessions go to the old version and new requests go to the new version.
Many SI sites repeat dawn stop-and-deploys without knowing this feature.
There is one more thing — if a deployment script doesn't wait for unpacking to finish, it reports a failed deployment
as a success. That is far worse than a stop-and-deploy.
Steps
- Start Tomcat and deploy
/opt/lab/samples/labhub-1.0.0.waras/opt/tomcat/webapps/labhub##001.war.http://127.0.0.1:8080/labhub/versionmust return1.0.0, andcatalina.outmust contain a deployment record forlabhub##001. (This deployment record is used as evidence in later steps.) - Check the unpacked directory and create
/root/tc/explode.csv. The first line ispath,exists. WriteY/Nfor the four paths below.WEB-INF/web.xml,WEB-INF/classes,WEB-INF/lib,META-INF - Deploy
/opt/lab/samples/labhub-2.0.0.waras/opt/tomcat/webapps/labhub##002.war. At this point, do not delete##001first and leave the two versions coexisting.http://127.0.0.1:8080/labhub/versionmust now return2.0.0, andcatalina.outmust contain deployment records for bothlabhub##001andlabhub##002. - Write
/root/tc/parallel.md. The body must contain the following three things.- Which version a request with an existing session goes to
- Which version a new request goes to
- Why notation such as
##1/##10is dangerous, since version strings are sorted by string comparison Both strings001and002must appear.
- Remove the
##001version completely (both the WAR and the unpacked directory). Even after removal,http://127.0.0.1:8080/labhub/versionmust still return2.0.0. - Start
/opt/lab/samples/labhub-boot.jarin the background on port 8082 and leave the log in/root/tc/boot.log.http://127.0.0.1:8082/versionmust return 200. - Create
/root/tc/war-vs-jar.csv. The first line is항목,WAR,JAR(item, WAR, JAR). There must be five items,배포단위(deployment unit),톰캣버전관리(Tomcat version management),포트지정(port specification),롤백(rollback) and무중단(zero downtime), and no cell may be empty. - Create
/root/tc/deploy-war.sh. It takes two arguments (WAR파일 컨텍스트명, the WAR file and the context name) and- If the WAR file doesn't exist or its size is 0, it ends immediately with a non-zero exit code.
- If it exists, it places it in
webappsand waits for unpacking by polling for up to 30 seconds. - If
http://127.0.0.1:8080/<컨텍스트명>/versionreturns 200, exit code 0; if it doesn't within 30 seconds, it ends with a non-zero exit code.
Notes
- Get only the status code with
curl -s -o /dev/null -w '%{http_code}' <URL>. - A WAR is a zip: you can see the contents with
unzip -l 파일.war(file.war). - Specifying the port of an executable JAR:
java -jar app.jar --server.port=8082 - Common mistake 1: verifying right after copying and judging the 404 as a failure. Polling is needed.
- Common mistake 2: while deploying
##002, deleting the##001WAR first. That is not a parallel deployment but just a replacement deployment. - Common mistake 3: a deployment script that always ends with 0 without verification. A script that reports a failed deployment as a success is the most dangerous.
Deploy the first version
Start Tomcat and deploy /opt/lab/samples/labhub-1.0.0.war as
/opt/tomcat/webapps/labhub##001.war.
http://127.0.0.1:8080/labhub/version must return 1.0.0, and
catalina.out must contain a deployment record for labhub##001.
(This deployment record is used as evidence in later steps.)
If you add ##version to the file name, the context path is the part before ##. Unpacking is asynchronous, so requesting right after copying can give a 404.
Check the unpacking result
Check the unpacked directory and create /root/tc/explode.csv.
The first line is path,exists. Write Y/N for the four paths below.
WEB-INF/web.xml, WEB-INF/classes, WEB-INF/lib, META-INF
A WAR is a zip. Look for the standard web application structure in the unpacked directory. Which parts are required and which are optional is the learning point of this step.
Deploy the second version in parallel
Deploy /opt/lab/samples/labhub-2.0.0.war as
/opt/tomcat/webapps/labhub##002.war.
At this point, do not delete ##001 first and leave the two versions coexisting.
http://127.0.0.1:8080/labhub/version must now return 2.0.0, and
catalina.out must contain deployment records for both labhub##001 and labhub##002.
Deploy with a higher version string on the same context name. Both WARs must be unpacked at the same time, and new requests must go to the higher version.
Summarize how session draining works
Write /root/tc/parallel.md. The body must contain the following three things.
- Which version a request with an existing session goes to
- Which version a new request goes to
- Why notation such as
##1/##10is dangerous, since version strings are sorted by string comparison Both strings001and002must appear.
Write out in sentences where existing sessions and new sessions each go in a parallel deployment. Also note that version strings are sorted by string comparison.
Take down the old version
Remove the ##001 version completely (both the WAR and the unpacked directory).
Even after removal, http://127.0.0.1:8080/labhub/version must still return 2.0.0.
You must remove the old WAR and the unpacked directory together. The service must not be interrupted even after removal, and the version response must remain the new version.
Start an executable JAR
Start /opt/lab/samples/labhub-boot.jar in the background on port 8082 and
leave the log in /root/tc/boot.log.
http://127.0.0.1:8082/version must return 200.
The embedded Tomcat approach gives the port as a launch argument. Start it in the background and leave the log in a file.
WAR vs JAR comparison table
Create /root/tc/war-vs-jar.csv. The first line is 항목,WAR,JAR (item, WAR, JAR).
There must be five items, 배포단위 (deployment unit), 톰캣버전관리 (Tomcat version management), 포트지정 (port specification), 롤백 (rollback) and 무중단 (zero downtime), and
no cell may be empty.
Try to organize it from the viewpoint that what governs the actual choice is not technical superiority but 'which way the operations procedures are standardized'.
Write the deployment script
Create /root/tc/deploy-war.sh. It takes two arguments (WAR파일 컨텍스트명, the WAR file and the context name) and
- If the WAR file doesn't exist or its size is 0, it ends immediately with a non-zero exit code.
- If it exists, it places it in
webappsand waits for unpacking by polling for up to 30 seconds. - If
http://127.0.0.1:8080/<컨텍스트명>/versionreturns 200, exit code 0; if it doesn't within 30 seconds, it ends with a non-zero exit code.
A script that reports a failed deployment as a success is the most dangerous. The polling loop that waits for unpacking and a non-zero exit code when verification fails are the key.