programing

가동 중지 시간을 최소화하면서 Java 웹앱을 배포하는 모범 사례?

nasanasas 2021. 1. 7. 08:04
반응형

가동 중지 시간을 최소화하면서 Java 웹앱을 배포하는 모범 사례?


대규모 Java 웹 앱 (> 100MB .war)을 배포 할 때 현재 다음 배포 프로세스를 사용하고 있습니다.

  • 애플리케이션 .war 파일은 개발 시스템에서 로컬로 확장됩니다.
  • 확장 된 애플리케이션은 개발 머신에서 라이브 환경으로 rsync : ed입니다.
  • rsync 후 라이브 환경의 앱 서버가 다시 시작됩니다. 이 단계는 꼭 필요한 것은 아니지만 배포시 응용 프로그램 서버를 다시 시작하면 빈번한 클래스로드로 인해 "java.lang.OutOfMemoryError : PermGen 공간"이 발생하지 않습니다.

이 접근 방식에 대한 좋은 점 :

  • rsync는 개발 머신에서 라이브 환경으로 전송되는 데이터의 양을 최소화합니다. 전체 .war 파일을 업로드하는 데 10 분이 넘게 걸리는 반면 rsync는 몇 초가 걸립니다.

이 접근 방식에 대한 나쁜 점 :

  • rsync가 실행되는 동안 파일이 업데이트되었으므로 애플리케이션 컨텍스트가 다시 시작됩니다. 이상적으로는 rsync가 아직 실행 중일 때가 아니라 완료된 후에 다시 시작해야합니다.
  • 앱 서버를 다시 시작하면 약 2 분의 다운 타임이 발생합니다.

다음 속성이있는 배포 프로세스를 찾고 싶습니다.

  • 배포 프로세스 중 다운 타임 최소화.
  • 데이터 업로드에 소요되는 최소 시간.
  • 배포 프로세스가 앱 서버와 관련된 경우 앱 서버는 오픈 소스 여야합니다.

질문:

  • 명시된 요구 사항을 고려할 때 최적의 배포 프로세스는 무엇입니까?

변경 사항을 WAR 파일로 푸시 할 때 rsync가 제대로 작동하지 않는다는 점이 언급되었습니다. 그 이유는 WAR 파일이 본질적으로 ZIP 파일이고 기본적으로 압축 된 멤버 파일로 생성되기 때문입니다. (압축 전) 멤버 파일을 조금만 변경하면 ZIP 파일에서 큰 규모의 차이가 발생하여 rsync의 델타 전송 알고리즘이 효과가 없게됩니다.

한 가지 가능한 해결책은 jar -0 ...원본 WAR 파일을 만드는 데 사용 하는 것입니다. -0옵션은 jarWAR 파일을 작성할 때 구성원 파일을 압축하지 않도록 명령에 지시 합니다. 그런 다음 rsync이전 버전과 새 버전의 WAR 파일을 비교할 델타 전송 알고리즘이 작은 차이를 생성 할 수 있어야합니다. 그런 다음 rsync가 diff (또는 원본 파일)를 압축 된 형식으로 보내도록 정렬합니다. 예를 들어 rsync -z ..., 압축 된 데이터 스트림을 사용 하거나 아래로 전송합니다.

편집 : WAR 파일의 구조에 따라 jar -0 ...구성 요소 JAR 파일을 생성 하는 데 사용할 수도 있습니다 . 이는 안정적인 타사 JAR 파일이 아니라 자주 변경되거나 단순히 다시 빌드되는 JAR 파일에 적용됩니다.

이론적으로이 절차는 일반 WAR 파일을 보내는 것보다 상당히 개선되어야합니다. 실제로 나는 이것을 시도하지 않았기 때문에 그것이 효과가있을 것이라고 약속 할 수 없습니다.

단점은 배포 된 WAR 파일이 훨씬 더 커진다는 것입니다. 이로 인해 웹앱 시작 시간이 길어질 수 있지만 효과가 미미할 것이라고 생각합니다.


완전히 다른 접근 방식은 WAR 파일을보고 거의 변경되지 않을 가능성이있는 라이브러리 JAR을 식별 할 수 있는지 확인하는 것입니다. 이러한 JAR을 WAR 파일에서 꺼내 Tomcat 서버의 common/lib디렉토리에 별도로 배포합니다 . 예를 들어 rsync.


최신 정보:

이 답변이 처음 작성된 이후로 다운 타임없이 tomcat에 war 파일을 배포하는 더 좋은 방법이 나타났습니다. 최신 버전의 바람둥이에서는 전쟁 파일 이름에 버전 번호를 포함 할 수 있습니다. 예를 들어 파일 ROOT##001.warROOT##002.war동일한 컨텍스트에 동시에 배포 할 수 있습니다 . 이후의 모든 것은 ##tomcat에 의해 컨텍스트 경로의 일부가 아닌 버전 번호로 해석됩니다. Tomcat은 앱의 모든 버전을 실행 상태로 유지하고 시작된 버전에서 이전 요청과 세션을 정상적으로 완료하는 동안 완전히 최신 버전으로 새로운 요청과 세션을 제공합니다. 버전 번호 지정은 tomcat 관리자와 심지어 catalina ant 작업을 통해서도 수행 할 수 있습니다. 여기에 더 많은 정보가 있습니다 .

원래 답변 :

Rsync는 델타 전송 알고리즘이 파일의 변경 사항을 찾고 압축되지 않은 파일의 작은 변경 사항이 결과 압축 버전을 크게 변경할 수 있기 때문에 압축 파일에서 비효율적 인 경향이 있습니다. 이러한 이유로 네트워크 대역폭이 병목 현상으로 판명되면 압축 된 버전이 아닌 압축되지 않은 war 파일을 재 동기화하는 것이 좋습니다.

배포를 수행하기 위해 Tomcat 관리자 애플리케이션을 사용하는 데있어 문제점은 무엇입니까? 원격 위치에서 전체 war 파일을 Tomcat 관리자 앱에 직접 업로드하지 않으려면 프로덕션 상자의 자리 표시 자 위치로 재 동기화 (위에 언급 된 이유로 압축되지 않음)하고 전쟁으로 다시 패키징 할 수 있습니다. 그런 다음 현지 관리자에게 전달하십시오. Tomcat과 함께 제공되는 멋진 ant 작업이있어 ​​Tomcat 관리자 앱을 사용하여 배포를 스크립팅 할 수 있습니다.

언급하지 않은 접근 방식에 추가적인 결함이 있습니다. 애플리케이션이 부분적으로 배포되는 동안 (rsync 작업 중에) 애플리케이션이 변경된 인터페이스가 동기화되지 않은 불일치 상태에있을 수 있으며, 새 / 업데이트 된 종속성이있을 수 있습니다. 또한 rsync 작업에 걸리는 시간에 따라 응용 프로그램이 실제로 여러 번 다시 시작될 수 있습니다. Tomcat에서 변경된 파일 수신 및 다시 시작 동작을 끌 수 있고 꺼야한다는 것을 알고 있습니까? 실제로 프로덕션 시스템에는 권장되지 않습니다. Tomcat 관리자 앱을 사용하여 언제든지 애플리케이션을 수동 또는 개미 스크립트로 다시 시작할 수 있습니다.

물론 다시 시작하는 동안 사용자는 애플리케이션을 사용할 수 없습니다. 그러나 가용성이 너무 걱정된다면로드 밸런서 뒤에 중복 웹 서버가있을 것입니다. 업데이트 된 war 파일을 배포 할 때 배포가 끝날 ​​때까지로드 밸런서가 모든 요청을 다른 웹 서버로 일시적으로 보내도록 할 수 있습니다. 다른 웹 서버에 대해 헹구고 반복하십시오.


In any environment where downtime is a consideration, you are surely running some sort of cluster of servers to increase reliability via redundancy. I'd take a host out of the cluster, update it, and then throw it back into the cluster. If you have an update that cannot run in a mixed environment (incompatible schema change required on the db, for example), you are going to have to take the whole site down, at least for a moment. The trick is to bring up replacement processes before dropping the originals.

Using tomcat as an example - you can use CATALINA_BASE to define a directory where all of tomcat's working directories will be found, separate from the executable code. Every time I deploy software, I deploy to a new base directory so that I can have new code resident on disk next to old code. I can then start up another instance of tomcat which points to the new base directory, get everything started up and running, then swap the old process (port number) with the new one in the load balancer.

스위치를 통해 세션 데이터를 보존해야하는 경우 모든 호스트에 세션 데이터를 복제 할 파트너가 있도록 시스템을 설정할 수 있습니다. 이러한 호스트 중 하나를 삭제하고 업데이트 한 다음 백업을 가져 와서 세션 데이터 백업을 선택한 다음 두 호스트를 전환 할 수 있습니다. 클러스터에 여러 쌍이있는 경우 모든 쌍의 절반을 드롭 한 다음 대량 전환을 수행하거나 릴리스 요구 사항, 기업 요구 사항 등에 따라 한 번에 쌍을 수행 할 수 있습니다. 그러나 개인적으로 나는 세션을 그대로 유지 한 상태에서 업그레이드를 시도하는 것보다 최종 사용자가 활성 세션의 매우 가끔 손실을 겪는 것을 허용하는 것을 선호합니다.

이는 모두 IT 인프라, 릴리스 프로세스 복잡성 및 개발자 노력 간의 균형입니다. 클러스터가 충분히 크고 원하는만큼 강력한 경우 대부분의 업데이트에 대해 다운 타임없이 교체 할 수있는 시스템을 설계하는 것이 쉽습니다. 업데이트 된 소프트웨어는 일반적으로 이전 스키마를 수용 할 수 없기 때문에 대규모 스키마 변경으로 인해 실제 다운 타임이 발생하는 경우가 많으며, 데이터를 새 db 인스턴스에 복사하고 스키마 업데이트를 수행 한 다음 서버를 새 db로 전환 할 수 없기 때문입니다. 새 DB가 복제 된 후 이전에 기록 된 데이터를 놓쳤을 것입니다. 물론 리소스가있는 경우 업데이트 된 모든 테이블에 새 테이블 이름을 사용하도록 새 앱을 수정하여 개발자에게 작업을 수행 할 수 있습니다. 그리고 이전 버전에서 이전 테이블에 기록 된 데이터로 새 테이블을 올바르게 업데이트하는 라이브 DB에 트리거를 배치 할 수 있습니다 (또는 뷰를 사용하여 다른 스키마에서 하나의 스키마를 에뮬레이션). 새 앱 서버를 가져 와서 클러스터로 교체하십시오. 빌드 할 개발 리소스가있는 경우 다운 타임을 최소화하기 위해 플레이 할 수있는 수많은 게임이 있습니다.

소프트웨어 업그레이드 중 다운 타임을 줄이는 가장 유용한 메커니즘은 앱이 읽기 전용 모드에서 작동 할 수 있는지 확인하는 것입니다. 그러면 사용자에게 몇 가지 필요한 기능이 제공되지만 데이터베이스 수정 등을 필요로하는 시스템 전체를 변경할 수 있습니다. 앱을 읽기 전용 모드로 설정 한 다음 데이터를 복제하고 스키마를 업데이트하고 새 DB에 대해 새 앱 서버를 가져온 다음 새 앱 서버를 사용하도록로드 밸런서를 전환합니다. 유일한 다운 타임은 읽기 전용 모드로 전환하는 데 필요한 시간과로드 밸런서의 구성을 수정하는 데 필요한 시간입니다 (대부분의 경우 다운 타임없이 처리 할 수 ​​있음).


내 조언은 분해 된 버전에서 rsync를 사용하지만 전쟁 파일을 배포하는 것입니다.

  1. 확장 된 버전의 webapp이있는 라이브 환경에 임시 폴더를 만듭니다.
  2. Rsync 분해 버전.
  3. rsync에 성공한 후 라이브 환경 시스템의 임시 폴더에 war 파일을 만듭니다.
  4. 서버 배포 디렉토리의 이전 전쟁을 임시 폴더의 새 전쟁으로 바꿉니다.

JBoss 컨테이너 (Tomcat 기반)에서는 이전 전쟁을 새 전쟁으로 교체하는 것이 좋습니다. 이는 원자적이고 빠른 작업이며 배포자가 시작할 때 전체 애플리케이션이 배포 된 상태에있을 것이기 때문입니다.


웹 서버에서 현재 웹 응용 프로그램의 로컬 복사본을 만들고 해당 디렉터리에 rsync 한 다음 심볼릭 링크를 사용하여 한 번의 "이동"에서 Tomcat이 다운 타임없이 새 배포를 가리 키도록 할 수 없습니까?


추출 된 전쟁을 rsync하는 방법은 꽤 좋습니다. 또한 프로덕션 서버에 핫 배포를 사용하지 않아야한다고 생각하므로 다시 시작하는 것도 좋습니다. 따라서 유일한 단점은 서버를 다시 시작해야 할 때 다운 타임입니다. 맞죠?

애플리케이션의 모든 상태가 데이터베이스에 있다고 가정하므로 일부 사용자는 한 앱 서버 인스턴스에서 작업하고 다른 사용자는 다른 앱 서버 인스턴스에서 작업하는 데 아무런 문제가 없습니다. 그렇다면,

두 개의 앱 서버 실행 : 번째 앱 서버 (다른 TCP 포트에서 수신 대기)를 시작하고 여기에 애플리케이션을 배포합니다. 배포 후 두 번째 앱 서버를 가리 키도록 Apache httpd의 구성 (mod_jk 또는 mod_proxy)을 업데이트합니다. Apache httpd 프로세스를 정상적으로 다시 시작합니다. 이렇게하면 다운 타임이없고 새로운 사용자와 요청이 자동으로 새 앱 서버로 리디렉션됩니다.

앱 서버의 클러스터링 및 세션 복제 지원을 사용할 수 있다면 두 번째 앱 서버가 시작되는 즉시 재 동기화되므로 현재 로그인 한 사용자에게도 원활하게 작동합니다. 그런 다음 첫 번째 서버에 대한 액세스가 없으면 종료합니다.


이는 애플리케이션 아키텍처에 따라 다릅니다.

내 애플리케이션 중 하나는로드 밸런싱 프록시 뒤에 위치하며, 여기서 시차 배포를 수행하여 다운 타임을 효과적으로 제거합니다.


서버에서 애플리케이션의 다운 타임을 최소화하거나 제거하기 위해 Java EAR 핫 배포 또는 Jboss 도구를 사용하여 Jboss에서 전쟁 종속성을 "핫"배포하는 방법 Eclipse 플러그인 에는 몇 가지 옵션이있을 수 있습니다.

다운 타임없이 클러스터에 배포하는 것도 흥미 롭습니다.

JavaRebel에는 핫 코드 배포 도 있습니다.


If static files are a big part of your big WAR (100Mo is pretty big), then putting them outside the WAR and deploying them on a web server (e.g. Apache) in front of your application server might speed up things. On top of that, Apache usually does a better job at serving static files than a servlet engine does (even if most of them made significant progress in that area).

So, instead of producing a big fat WAR, put it on diet and produce:

  • a big fat ZIP with static files for Apache
  • a less fat WAR for the servlet engine.

Optionally, go further in the process of making the WAR thinner: if possible, deploy Grails and other JARs that don't change frequently (which is likely the case of most of them) at the application server level.

If you succeed in producing a lighter WAR, I wouldn't bother of rsyncing directories rather than archives.

Strengths of this approach:

  1. The static files can be hot "deployed" on Apache (e.g. use a symbolic link pointing on the current directory, unzip the new files, update the symlink and voilà).
  2. The WAR will be thinner and it will take less time to deploy it.

Weakness of this approach:

  1. There is one more server (the web server) so this add (a bit) more complexity.
  2. You'll need to change the build scripts (not a big deal IMO).
  3. You'll need to change the rsync logic.

I'm not sure if this answers your question, but I'll just share on the deployment process I use or encounter in the few projects I did.

Similiar to you, I do not ever recall making a full war redeployment or update. Most of the time, my updates are restricted to a few jsp files, maybe a library, some class files. I am able to manage and determine which are the affected artifacts, and usually, we packaged those update in a zip file, along with an update script. I will run the update script. The script does the following:

  • Backup the files that will be overwritten, maybe to a folder with today's date and time.
  • Unpackage my files
  • Stop the application server
  • Move the files over
  • Start the application server

If downtime is a concern, and they usually are, my projects are usually HA, even if they are not sharing state but using a router that provide sticky session routing.

Another thing that I am curious would be, why the need to rsync? You should able to know what are the required changes, by determining them on your staging/development environment, not performing delta checks with live. In most cases, you would have to tune your rsync to ignore files anyway, like certain property files that define resources a production server use, like database connection, smtp server, etc.

I hope this is helpful.


At what is your PermSpace set? I would expect to see this grow as well but should go down after collection of the old classes? (or does the ClassLoader still sit around?)

Thinking outloud, you could rsync to a separate version- or date-named directory. If the container supports symbolic links, could you SIGSTOP the root process, switch over the context's filesystem root via symbolic link, and then SIGCONT?


As for the early context restarts. All containers have configuration options to disable auto-redeploy on class file or static resource changes. You probably can't disable auto redeploys on web.xml changes so this file is the last one to update. So if you disable to auto redeploy and update the web.xml as the last one you'll see the context restart after the whole update.


We upload the new version of the webapp to a separate directory, then either move to swap it out with the running one, or use symlinks. For example, we have a symlink in the tomcat webapps directory named "myapp", which points to the current webapp named "myapp-1.23". We upload the new webapp to "myapp-1.24". When all is ready, stop the server, remove the symlink and make a new one pointing to the new version, then start the server again.

We disable auto-reload on production servers for performance, but even so, having files within the webapp changing in a non-atomic manner can cause issues, as static files or even JSP pages could change in ways that cause broken links or worse.

In practice, the webapps are actually located on a shared storage device, so clustered, load-balanced, and failover servers all have the same code available.

The main drawback for your situation is that the upload will take longer, since your method allows rsync to only transfer modified or added files. You could copy the old webapp folder to the new one first, and rsync to that, if it makes a significant difference, and if it's really an issue.


Tomcat 7 has a nice feature called "parallel deployment" that is designed for this use case.

The gist is that you expand the .war into a directory, either directly under webapps/ or symlinked. Successive versions of the application are in directories named app##version, for example myapp##001 and myapp##002. Tomcat will handle existing sessions going to the old version, and new sessions going to the new version.

The catch is that you have to be very careful with PermGen leaks. This is especially true with Grails that uses a lot of PermGen. VisualVM is your friend.


Just use 2 or more tomcat servers with a proxy over it. That proxy can be of apache/nignix/haproxy.

Now in each of the proxy server there is "in" and "out" url with ports are configured.

First copy your war in the tomcat without stoping the service. Once war is deployed it is automatically opened by the tomcat engine.

Note cross check unpackWARs="true" and autoDeploy="true" in node "Host" inside server.xml

It look likes this

  <Host name="localhost"  appBase="webapps"
        unpackWARs="true" autoDeploy="true"
        xmlValidation="false" xmlNamespaceAware="false">

Now see the logs of tomcat. If no error is there it means it is up successfully.

Now hit all APIs for testing

Now come to your proxy server .

Simply change the background url mapping with the new war's name. Since registering with the proxy servers like apache/nignix/haProxy took very less time, you will feel minimum downtime

Refer -- https://developers.google.com/speed/pagespeed/module/domains for mapping urls


You're using Resin, Resin has built in support for web app versioning.

http://www.caucho.com/resin-4.0/admin/deploy.xtp#VersioningandGracefulUpgrades

Update: It's watchdog process can help with permgenspace issues too.


Not a "best practice" but something I just thought of.

How about deploying the webapp through a DVCS such as git?

This way you can let git figure out which files to transfer to the server. You also have a nice way to back out of it if it turns out to be busted, just do a revert!


I wrote a bash script that takes a few parameters and rsyncs the file between servers. Speeds up rsync transfer a lot for larger archives:

https://gist.github.com/3985742

ReferenceURL : https://stackoverflow.com/questions/1640333/best-practices-for-deploying-java-webapps-with-minimal-downtime

반응형