Friday, November 4, 2011

Using MOCK to build packages for RHEL distros

MOCK is a python based powerful package build tool ( I would have called it a system, but it is basically rpmbuild under chroot ). I use it a lot, mainly because the servers I am working on are RHEL and my workstation is Fedora. It is very powerful, because it enables you to build packages in a chroot environment and build them on a RHEL based distro, for any RHEL based distro or architecture, either from source or source RPM. It is no wonder this is what the guys from RHEL are using to build the packages for all distros. First, you must install it.
yum install mock
should do it. Then, you must add your user to the mock group, since mock requires root permissions - for chroot and such.
root@localhost$ usermode -aG mock [your-user] 
Now you are ready to experience the power of MOCK. Its configuration files are in /etc/mock/ (surprise surprise). There is a global confguration file - /etc/mock/site-defaults.cfg , and there are the distro configuration files - like epel-5-i386.cfg, epel-5-x86_64.cfg - each containing the distro specific configurations. The global configuration variables ( /etc/mock/site-defaults.cfg ) cant be overwritten in each of the config files. The options I find most useful are
#This uses the pigz instead of gzip
#Pigz is way faster on multi-core systems (uses parallel processing) with zipping/unzipping
#You should yum install it if it is not already installed

config_opts['plugin_conf']['root_cache_opts']['compress_program'] = "pigz"

# bind mount plugin is enabled by default but has no configured directories to mount
#It is used to bind mount a directory in your direcotry tree to MOCK's chroot
#They are specified in ("root_dir","chroot_dir") tuples, using python "append" list method ( For PythonProgrammers: extend, insert and other list methods should also work)

config_opts['plugin_conf']['bind_mount_enable'] = True
config_opts['plugin_conf']['bind_mount_opts']['dirs'].append(('/opt/intel', '/opt/intel' ))

#This should mount /var/lib/mock/ in your ramdisk, but for some reason, 
#this does not work for men, so a workaround is presented*

#config_opts['plugin_conf']['tmpfs_enable'] = True
#config_opts['plugin_conf']['tmpfs_opts']['required_ram_mb'] = 16384
#config_opts['plugin_conf']['tmpfs_opts']['max_fs_size'] = '8192m'
*Workaround for TMPFS Add this to /etc/fstab
mock_root                       /var/lib/mock/          tmpfs   uid=0,gid=395,mode=02775  0 0 # Gid should be the GID of the MOCK group
And
mount -a
Using mock is fairly simple:

Build SRPM

mock -r epel-5-x86_64 --buildsrpm --sources="build_source.tar.bz" --spec "build_spec.spec" --resultdir="$HOME/mock/output/" --no-cleanup-after
-r - specifies the build distro and architecture
--buildsrpms - tells mock it should build SOURCE RPM
--spec - the spec file mock should use when building the script
--resultdir - where the output and logs of mock execution will be put
--no-cleanup-after - tells mock it should not delete its chroot when finished (good for debbugging)

Build RPM from SRPM

mock -r epel-5-x86_64 --no-clean --rebuild "/home/[user]/mock/output/*.src.rpms" --resultdir="$HOME/mock/output/" --no-cleanup-after
--noclean - tells mock not to clean up the chroot env before building ( clean means *delete* and *rebuild*)

Install package to chroot from outside chroot

This is the only way since the chroot does not have its own YUM. If you want to
mock -r  epel-5-x86_64 --install compat-libstdc++-33.x86_64 man --resultdir="$HOME/mock/output/"
--install - this will install packages specified inside MOCK chroot

Copy files inside chroot

mock -r  epel-5-x86_64 --copyin /tmp/stuff --resultdir="$HOME/mock/output/"
--copyin - copies files inside the MOCK chroot, use this only when mount bind is not possible

Initialize chroot without building anything

mock -r  epel-5-x86_64 --init --resultdir="$HOME/mock/output/"

Clean/Remove chroot

mock -r  epel-5-x86_64 --clean --resultdir="$HOME/mock/output/"

Open a shell in the chroot

mock -r  epel-5-x86_64 --shell --resultdir="$HOME/mock/output/"
NB! Without --resultdir all mock commands fail

Running nginx + supervisor + uwsgi + django

Running nginx + supervisor + uwsgi + django is a great way to run your django apps. Supervisor is a great tool for running foreground applications in background. It does not stop you from running background (daemon) applications too as long as you can run them in "no-fork" mode. It makes it fairly easy to manage all your applications, not only django websites. You can download the latest version from http://supervisord.org/ It has a great documentation and is very easy to configure. You just add your program in the configuration file and reload the supervisor (strangely enough, reload is called update) and voila, your new application is registered in the supervisor pool, up and running. You can add all programs in one big file - /etc/supervisor.conf, but I prefer the very handy "include" section. There, you can specify a directory that will contain your programs .ini files
---cut---
[include]
files = supervisord.d/*.ini
UWSGI is an "application container server" as they call it themselves. It is used to provide a "gateway" between your web server and web application. It has a million configuration options, most of which are well documented. You can get it from http://projects.unbit.it/uwsgi/. You migh can pip install uwsgi ,but I prefer compiling from source to RPM. So, I wont get into much details, because there are thousands of tutorials about django and uwsgi. I assume that you already have your already installed django, nginx and your django website put somewhere, for example /srv/www/djsite So the supervisord config for your app will look like this:
[program:djsite]
command=/usr/bin/uwsgi 
        --socket /var/run/django-sites/sock/djsite.sock #This is the socket that will be used for connection between nginx and you django site 
        --processes 4 #This is the number of sub-processes that uwsgi will spawn
        --max-requests 1024 #The max number of request your site can handle
        --vacuum #This will delete the socket on shutdown
        --harakiri 20 #This is where UWSGI shines! This will kill an uwsgi sub-process if a request is send to it and it does return response for 20 second
        --post-buffering 4096 #This is required for harakiri to work properly. You should have it, even if you did not set harakiri mode
        --pp /srv/www/djsite # If your project is not on the PYTHONPATH put the project dir here. You can specify additional resources with multiple --pp options
        --module django.core.handlers.wsgi:WSGIHandler()  #This is the django WSGI handler, that will handle the communication of your application

environment=DJANGO_SETTINGS_MODULE='djsite.settings' # Your site settings module
--cut-- # Some additional options you can specify depending on your needs
stopwaitsecs=16 # Supervisor expects the process to stop in 16s, if it doesn't it sends him KILL signal
stopsignal=INT # This is very important! Explained bellow.

#User and group is nginx, so nginx can write into the defined socket
user=nginx
group=nginx
It is very important to set the stop signal to INT or QUIT, because the default signal that supervisor sends is TERM. UWSGI handles TERM, and restarts the master process and the subprocesses. After 16 seconds of not exiting, supervisor thinks your process is hanging and sends KILL, which kills the master process, but the sub-processes are inherited by INIT. This causes a lot of troubles. Dont forget to create /var/run/django-sites/sock/ with the appropriate permissions. Configure nginx:
server {
#configure your server
 --cut--
include uwsgi_params; #ncludes the parameters used by the uwsgi nginx module
 --cut--
#Add a location for your django site
 location / {
     uwsgi_pass unix:/var/run/django-sites/sock/djsite.sock; # This should point to your uwsgi socket.
  }
#Serve your static content with nginx!
 location ~ ^/(images|javascript|js|css|flash|media|static)/  {
      root /srv/www/static/djsite;
 }
}
Don't forget to include your uwsgi_params in the server config. Reload your server
service nginx reload
If supervisor is running, you should make it reread its configuration. $supervisorctl update should do that
supervisorctl update
And that is about it, you should have a running nginx-supervisor-uwsgi-django.