forked from SquareBracketAssociates/EnterprisePharo
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' of github.com:SquareBracketAssociates/Enterpris…
…ePharo
- Loading branch information
Showing
5 changed files
with
343 additions
and
133 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
!Deploying a Pharo Web Application in Production | ||
|
||
In the previous chapters we discussed several frameworks and libraries to facilitate the development of web application. In this chapter, we will focus on deploying such a web application. While doing so, we will try to answer some questions such as: which operating system should I use, how do I run my application, how do I ensure my application will be restarted after a reboot or a crash, and how do I log data. | ||
|
||
!!Where to Host your Application? | ||
|
||
The easiest (and fastest) way to host your application is to host it in the cloud. | ||
|
||
*PharoCloud>http://pharocloud.com*, for example, proposes pre-packaged solutions (including Seaside and Pier, database support) as well as the possibility to use your own Pharo image. You could start very quickly from there but you do not have full control on your pharo stack. It is enough in most cases: PharoCloud manages defaults for you. | ||
|
||
There are many other cloud providers including *Amazon AWS>https://aws.amazon.com/*, *Openshit>https://www.openshift.com/*, *OVH>https://www.ovh.com/fr* and *Microsoft Azure>http://azure.microsoft.com*. Many Pharo users use *DigitalOcean>https://www.digitalocean.com/* as it is both simple and cheap. | ||
|
||
Choose your cloud provider according to your needs. | ||
|
||
In the rest of this chapter, we detail how to setup a server to host a Pharo web application. | ||
|
||
!!Which Operating System? | ||
|
||
A lot of pharo developers use Mac OS X to develop their applications but it is not really a solution for a production deployment as there is no more Apple server hardware. Most Pharo developers prefer to deploy on GNU/Linux because GNU/Linux stability is well known. Deploying on Windows is a bit more complex and less supported by cloud providers. | ||
|
||
There are many Linux distributions you can choose from. If you restrict your choice to well-known open-source distributions, competitors are *Centos>http://www.centos.org*, *Debian>http://www.debian.org* and *Ubuntu>http://www.ubuntu.com*. Most distribution will do the job, choose the most appropriate for you. A long-term support (''aka.'', LTS) version is welcomed if you do not want to update your operating system too often. The Pharo Virtual Machine (VM) comes pre-packaged for some distributions. For other distributions, you will have to compile the VM sources yourself. Pay attention that the Pharo VM is still 32bits as of 2015 and you will have to install 32-bit libraries on your 64-bit Operating System. | ||
|
||
!!Build your Image | ||
|
||
The best option to obtain a clean image to deploy is to start from a fresh *stable pharo image>http://files.pharo.org/image/stable/latest.zip* and to install required packages through your application's Metacello configuration and the command line handler. The configuration has to explicitely describes all dependencies used in your application. | ||
|
||
First, create a copy of the clean image with your application name: | ||
|
||
[[[language=bash | ||
$ ./pharo Pharo.image save myapp | ||
]]] | ||
|
||
Then, install your dependencies: | ||
[[[language=bash | ||
$ ./pharo myapp.image config http://www.smalltalkhub.com/mc/Me/MyApp/main ConfigurationOfMyApp --install=stable | ||
=============================================================================== | ||
Notice: Installing ConfigurationOfMyApp stable | ||
=============================================================================== | ||
[...] | ||
]]] | ||
|
||
After loading all necessary code, the ==config== option will also save the image so that the image now permanently includes your code. | ||
|
||
To make sure that your deployment image is reproducible, the best approach is to create a Continuous Integration job that automatically produces clean deployment-ready images of your application. | ||
|
||
!!Run your Application | ||
|
||
When you have a Pharo image with your application inside, the next step is to start the application. To make this process reproducible, it is recommended to create a dedicated file (''e.g.'', named ==myapp.st==) with the instructions needed to start your application. Here is an example of a script used to start a web application using Zinc. | ||
|
||
[[[language=Smalltalk | ||
ZnServer defaultOn: 8080. | ||
ZnServer default logToStandardOutput. | ||
ZnServer default delegate | ||
map: 'image' to: MyFirstWebApp new; | ||
map: 'redirect-to-image' to: [ :request | ZnResponse redirect: 'image' ]; | ||
map: '/' to: 'redirect-to-image'. | ||
ZnServer default start | ||
]]] | ||
This script will start an instance of the Zinc Web Server on localhost on the port 8080 and store it as the default instance. We configured the Zinc instance to log on the standard output, we changed the default root (/) handler to redirect to your new /image web app. ==MyFirstWebApp== class handles HTTP requests by implementing the ==#handleRequest:== message. | ||
|
||
You can test the startup script like this: | ||
|
||
[[[language=bash | ||
$ ./pharo myapp.image myapp.st | ||
2013-07-10 11:46:58 660707 I Starting ZnManagingMultiThreadedServer HTTP port 8080 | ||
2013-07-10 11:46:58 670019 D Initializing server socket | ||
2013-07-10 11:47:12 909356 D Executing request/response loop | ||
2013-07-10 11:47:12 909356 I Read a ZnRequest(GET /) | ||
2013-07-10 11:47:12 909356 T GET / 302 16B 0ms | ||
2013-07-10 11:47:12 909356 I Wrote a ZnResponse(302 Found text/plain;charset=utf-8 16B) | ||
2013-07-10 11:47:12 909356 I Read a ZnRequest(GET /image) | ||
2013-07-10 11:47:12 909356 T GET /image 200 282B 0ms | ||
2013-07-10 11:47:12 909356 I Wrote a ZnResponse(200 OK text/html;charset=utf-8 282B) | ||
2013-07-10 11:47:12 909356 I Read a ZnRequest(GET /image?raw=true) | ||
2013-07-10 11:47:12 909356 T GET /image?raw=true 200 18778B 82ms | ||
2013-07-10 11:47:12 909356 I Wrote a ZnResponse(200 OK image/png 18778B) | ||
]]] | ||
|
||
Type ==Ctrl-c== to kill the server. | ||
|
||
In Unix systems, init scripts are used to automatically start services when the server reboots and to monitor the status of these services. These scripts typically have a ==start== command, a ==stop== command and a ==status== command (some init-scripts have more than these 3 commands). Your application's init script should be configured to be automatically executed when the server restarts. This init script must typically be placed in the ''/etc/init.d'' directory. | ||
|
||
You can find a *template for such an init script>https://github.com/pharo-project/pharo-deployment-scripts/blob/master/pharo-service-script.sh* in the *GitHub pharo-project/pharo-deployment-scripts repository>https://github.com/pharo-project/pharo-deployment-scripts*. Give this script the name of your application. This script is derived from the template provided by the Ubuntu distribution in ==/etc/init.d/skeleton==. | ||
|
||
The *pharo-run-script.sh>https://github.com/pharo-project/pharo-deployment-scripts/blob/master/pharo-run-script.sh* is another useful script. It runs a Pharo image with a pre-defined Smalltalk script file to evaluate (the ==myapp.st== you wrote above). You may need to edit this file to configure the Pharo VM path or options. | ||
|
||
Create a folder with your application name ''myapp'', then copy the ==pharo-run-script.sh== script into this folder. Give the script execution permissions: ==chmod a+x ./myapp==. | ||
|
||
You should end with a file hierarchy like this one: | ||
- /etc/init.d/myapp (init script) | ||
- /opt/myapp | ||
--myapp (generic pharo run script) | ||
--myapp.st (image startup script) | ||
--myapp.image | ||
--myapp.changes | ||
- /usr/bin/pharo-vm-nox | ||
|
||
When the file hierarchy is ready, you can start your application by executing the ==./myapp== script or by using the init script through ==service myapp start==. | ||
|
||
When the Pharo image crashes (this ''will'' happen), there must be a way to automatically recover from this crash. For this to work, (1) the application data must be backed up, (2) there must be a way to know when the application has crashed, and (3) there must be a way to automatically restart the application. In the following we cover these points. | ||
|
||
!!Backup | ||
|
||
You do not want to lose data! To do that, the simplest solution is to make your image stateless: if your image crashes, no data should be lost because no data is in the image. If your application requires persistent data (''e.g.'', user accounts), the best is to use a database (''e.g.'', PostgreSQL, MongoDB). You must then make sure that your database is backed up properly. | ||
|
||
!!Monitoring | ||
|
||
To automatically restart your application when it crashes, there must be a way to detect that it has crashed. | ||
|
||
With a standard operating system's init script such as the one described above, you can use the ==status== command to detect if Pharo is running or not. Nevertheless, Pharo can be running (the ==status== command detects it fine) but not responding (''i.e.'', HTTP requests never get answered). This can happen if the image is frozen for example. If your application is a web application, you may wish to check if your application is able to answer to HTTP requests. To do so, you need to define some ==ping== url to your application that will be used by the monitoring tool. | ||
|
||
A simple solution to both monitor your application and take appropriate actions (''e.g.'', restart) is to use the *monit utility>https://mmonit.com/monit*. | ||
|
||
You can configure Monit through the ==/etc/monit/monitrc== file. | ||
|
||
!!!Monit Global Configuration | ||
|
||
!!!!Monit Dashboard | ||
|
||
You can first activate the embedded HTTP dashboard of monit. We will only allow local connections with a given username/password. | ||
|
||
[[[language=monit | ||
set httpd port 2812 and | ||
use address localhost # only accept connection from localhost | ||
allow localhost # allow localhost to connect to the server and | ||
allow admin:monit # require user 'admin' with password 'monit' | ||
]]] | ||
|
||
Do not forget to apply the new configuration: | ||
|
||
[[[language=bash | ||
$ sudo monit reload | ||
]]] | ||
|
||
To connect from a different place than localhost, use an SSH tunnel. If the server running both your application and monit is named ==myserver.com==, then you can execute the following to connect to your server and open a local port: | ||
|
||
[[[language=bash | ||
$ ssh -L 2812:localhost:2812 myserver.com | ||
]]] | ||
|
||
Keep the SSH connection open, and browse to *http://localhost:2812>http://localhost:2812* to display the monit dashboard. | ||
|
||
!!!!Email Settings | ||
|
||
If you want notifications from monit, you need to configure email settings so that monit can send emails. Edit the monit configuration file again and add the following line: | ||
|
||
[[[language=monit | ||
set mailserver <smtp.domain> | ||
]]] | ||
|
||
!!!Monitor System Services | ||
Configuration files related to an application (or a service) should be put into the ==/etc/monit/monitrc.d== directory (more modular that everything in the core configuration file). To enable a configuration just symlink it to ==conf.d==. | ||
We will first enable pre-defined configuration for SSH. | ||
[[[language=bash | ||
$ sudo ln -s /etc/monit/monitrc.d/openssh-server /etc/monit/conf.d/openssh-server | ||
$ sudo monit reload]]] | ||
""Warning"": default configurations for well-known services are provided by monit but may require some adaptations (ex: wrong path to the PID file). | ||
|
||
To check for errors, you may need to run monit in verbose mode: | ||
|
||
[[[language=bash | ||
$ sudo monit -v | ||
]]] | ||
|
||
and check the monit error log, by default in ==/var/log/monit.log==. | ||
|
||
!!!Monit Configuration to Control a Pharo Application | ||
|
||
Application-specific configuration files must be added to the ==/etc/monit/monitrc.d== directory. Create a new ==myapp== file in this directory: | ||
|
||
[[[language=monit | ||
alert [email protected] | ||
|
||
check process myapp with pidfile /var/run/myapp.pid | ||
start program = "/etc/init.d/myapp start" | ||
stop program = "/etc/init.d/myapp stop" | ||
if 5 restarts within 5 cycles | ||
then timeout | ||
]]] | ||
|
||
With this in place, when a problem occurs, the ''alert'' instruction makes sure [email protected]== is notified by email. The monitoring is described with the check command. We ask monit to check a given pid file. If there is no pid or no process associated to the pid, monit will start the program with the given instruction. The last instruction prevents infinite loops if there is a problem with the script. | ||
We now activate the monitoring of myapp: | ||
[[[language=bash | ||
$ sudo ln -s /etc/monit/monitrc.d/myapp /etc/monit/conf.d/myapp | ||
$ sudo monit reload | ||
]]] | ||
|
||
!!!Monit Configuration for a Pharo Web Application | ||
|
||
A Pharo image may be running (alive process) but not responding to HTTP requests anymore. In such cases, your application becomes unusable. This state can be verified by sending a simple HTTP request and checking for the response. We can ask monit to monitor your web server by doing regular checks to a predifined URL and validate the HTTP response content: | ||
|
||
[[[language=monit | ||
alert [email protected] | ||
|
||
check process myapp with pidfile /var/run/myapp.pid | ||
start program = "/etc/init.d/myapp start" | ||
stop program = "/etc/init.d/myapp stop" | ||
if failed (url http://localhost:8080/ping | ||
and content == "pong" | ||
and timeout 10 seconds) | ||
then restart | ||
if 5 restarts within 5 cycles | ||
then timeout | ||
]]] | ||
|
||
This configuration will try to connect to the ==/ping== URL on localhost. If monit can not connect, or no answer arrives before 10 seconds, or the content is not exactly ==pong==, monit will restart the application. | ||
|
||
You may also want to monitor Apache if there is an Apache server in front of your application. You can do that by adapting the already existing ==apache2== monit configuration file: | ||
|
||
[[[language=monit | ||
if failed host localhost port 80 with protocol http with timeout 25 seconds for 4 times within 5 cycles then restart | ||
]]] | ||
|
||
Activate the Apache monitoring and reload the monit configuration: | ||
|
||
[[[language=bash | ||
$ sudo ln -s /etc/monit/monitrc.d/apache2 /etc/monit/conf.d/apache2 | ||
$ sudo monit reload | ||
]]] | ||
|
||
% LocalWords: monit |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.