How to run a Flask application in Docker

Flask is a nice web application framework for Python.

My example app.py looks like:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
  return 'Hello, World!'

According to Flask documentation, to run the application we need to run FLASK_APP=app.py flask run. So our Dockerfile will run this command and we’ll pass an environment variable with the application name when we start the container:

FROM python:3-onbuild
EXPOSE 5000
CMD [ "python", "-m", "flask", "run", "--host=0.0.0.0" ]

The --host=0.0.0.0 parameter is necessary so that we will be able to connect to flask from outside the docker container.

Using the -onbuild version of the Python container is handy because it will import a file named requirements.txt and install the Python modules listed in it, so go on and create this file in the same directory, containing the single line, flask.

Now we can build our container:

docker build -t flaskapp .

This might take a while. When it ends, we’ll be able to run the container, passing the FLASK_APP environment variable:

docker run -it --rm --name flaskapp \
  -v "$PWD":/usr/src/app -w /usr/src/app \
  -e LANG=C.UTF-8 -e FLASK_APP=app.py \
  -p 5000:5000 flaskapp

As you can see I’m mounting the local directory $PWD to /usr/src/app in the container and setting the work directory there. I’m also passing the -p 5000:5000 parameter so that the container tcp port 5000 is available by connecting to my host machine port 5000.

You can test your app with your browser or with curl:

$ curl http://127.0.0.1:5000/
Hello, World!

I hope this will be useful to someone out there, have fun! :)

How to fix Fedora 25 dnf upgrade “certificate expired” failure

After logging on a Fedora 25 system I didn’t log for a while, I ran dnf clean all ; dnf upgrade to update it, but I ran into this problem:

# dnf -vvvv -d 5 upgrade
cachedir: /var/cache/dnf
Loaded plugins: playground, builddep, config-manager, debuginfo-install, generate_completion_cache, needs-restarting, copr, protected_packages, noroot, download, Query, reposync
DNF version: 1.1.10
Cannot download 'https://mirrors.fedoraproject.org/metalink?repo=updates-released-f25&arch=x86_64': Cannot prepare internal mirrorlist: Curl error (60): Peer certificate cannot be authenticated with given CA certificates for https://mirrors.fedoraproject.org/metalink?repo=updates-released-f25&arch=x86_64 [Peer's Certificate has expired.].
Errore: Failed to synchronize cache for repo 'updates'

The certificates expired and dnf refuses to work. To update the certificates, I simply installed with rpm the packages ca-certificates, p11-kit, p11-kit-trust, openssl and openssl-libs from the distro upgrades.

# rpm -Uvh http://www.nic.funet.fi/pub/mirrors/fedora.redhat.com/pub/fedora/linux/updates/25/x86_64/c/ca-certificates-2017.2.11-1.1.fc25.noarch.rpm \ http://www.nic.funet.fi/pub/mirrors/fedora.redhat.com/pub/fedora/linux/updates/25/x86_64/p/p11-kit-0.23.2-3.fc25.x86_64.rpm \ http://www.nic.funet.fi/pub/mirrors/fedora.redhat.com/pub/fedora/linux/updates/25/x86_64/p/p11-kit-trust-0.23.2-3.fc25.x86_64.rpm \
http://www.nic.funet.fi/pub/mirrors/fedora.redhat.com/pub/fedora/linux/updates/25/x86_64/o/openssl-1.0.2k-1.fc25.x86_64.rpm \ http://www.nic.funet.fi/pub/mirrors/fedora.redhat.com/pub/fedora/linux/updates/25/x86_64/o/openssl-libs-1.0.2k-1.fc25.x86_64.rpm
# dnf upgrade
Fedora 25 - x86_64 - Updates  
Fedora 25 - x86_64
Ultima verifica della scadenza dei metadati: 0:00:19 fa il Mon Apr 17 13:47:09 2017.
[...]

You might have to find the latest version by browsing around your favourite Fedora mirror (you can find the base url in /etc/yum.repos.d/fedora-updates.repo).

Some links worth reading:

PHP Sessions on MS Azure Redis service

I sure hope you’ll never end up facing this, but in case you do indeed have a PHP application on MS Azure, and you want to use MS Azure Redis service for the session backend, you’ll have to set your session.save_path to something like:

session.save_path='tcp://UNIQUENAME.redis.cache.windows.net:6379?auth=MSHATESYOU4FUNandPROFIT=&timeout=1&prefix=OHNOMS'

Easy enough, unless your auth key happens to contain a + symbol. In that case, your PHP session creation will fail with this error:

# php count.php
PHP Fatal error:  Uncaught exception 'RedisException' with message 'Failed to AUTH connection' in count.php:3
Stack trace:
#0 count.php(3): session_start()
#1 {main}
  thrown in count.php on line 3
PHP Fatal error:  Uncaught exception 'RedisException' with message 'Failed to AUTH connection' in [no active file]:0
Stack trace:
#0 {main}
  thrown in [no active file] on line 0

From redis-cli the authentication was working fine, so it took us a while to debug. It ended up being a problem with the + symbol, and the the quickest solution in our case was to just regen the auth key so it didn’t have a + in it, but I suspect (but I didn’t test this solution) that URLencoding the + as %2B might work as well.

Pane di farro mini-HOWTO

Questo mini-HOWTO spiega brevemente come fare il pane con la farina di farro.

Update: aggiornato il procedimento in seguito a successivi esperimenti! Ora il pane viene ancora più morbido!

Soddisfatto del mio 5° esperimento, voglio condividere con l’Internet la ricetta e le varie note d’interesse da tenere in mente.

La ricetta che ho seguito inizialmente prevede:

  • 300g di farina di farro (nel mio caso integrale, ma fate come vi pare)
  • 100g di farina bianca (tipo 00)
  • 250ml d’acqua tiepida
  • 3 o 4g di lievito di birra
  • uno o due cucchiaini di sale, secondo i gusti
  • un paio di cucchiai d’olio d’oliva

A questa lista, nell’ultimo esperimento ho provato ad aggiungere anche:

  • un cucchiaino di zucchero bianco

per favorire la lievitazione.

Strumenti necessari

  • contenitore graduato o bilancia per misurare sia la farina che l’acqua
  • contenitore per scaldare l’acqua (pentola o bollitore)
  • contenitore per lasciare il lievito ad “attivarsi” nell’acqua (una ciotolina grande abbastanza per contenere 250ml d’acqua)
  • scodella per impastare il pane (vedi note più avanti)
  • forno (eh)
  • teglia adatta al summenzionato forno
  • guanti da forno per maneggiare la teglia bollente
  • carta da forno
  • stoviglie varie: cucchiai, cucchiaini, coltelli, etc
  • un panno morbido e pulito (nel mio caso, di cotone)

Preparativi

La prima cosa da fare è mettere l’acqua a scaldare: non serve farla bollire, vi servirà tiepida, cioè più calda della temperatura ambiente, ma non abbastanza da scottarvi se ci mettete un dito dentro.

Mentre l’acqua si scalda, iniziate a misurare la farina.

Nota importante: non mettete via la farina dopo averla misurata! Quasi sicuramente ve ne servirà qualche altro pizzico durante la lavorazione.

Una volta che l’acqua è tiepida, mettetela in una ciotolina col lievito e un cucchiaino di zucchero, mescolate un po’ e lasciate il composto da parte: dopo qualche minuto dovrebbe fare una bella schiuma scura tipo quella della Guinness. Quello è il Segno[tm] che il lievito si è “attivato” correttamente.

Nota: l’acqua troppo fredda non “attiva” il lievito, mentre quella troppo calda lo uccide. Non esagerate in un senso o nell’altro!

Mentre aspettate che il lievito si attivi, mettete nella Scodella Per Impastare Il Pane tutta la farina e il sale, mischiateli bene e lasciate un buco al centro (la cosiddetta “fontanella”), dove più tardi verserete l’acqua col lievito.

Nota: la Scodella Per Impastare Il Pane è una scodella di acciaio inox piuttosto larga con il fondo in gomma per mantenerla ferma e attutire un po’ le botte sul tavolo durante la lavorazione dell’impasto. Geniale.

Scodella Per Impastare Il Pane

Nota: c’è chi dice di mettere il sale successivamente, perché può rallentare/diminuire la lievitazione. Non ho ancora verificato come si comporta l’impasto facendo in quel modo, ma mettendolo a questo punto, nella mia limitata esperienza non si sono verificati problemi.

L’impasto

Quando l’acqua col lievito ha sviluppato una bella schiuma, versatela nel “buco” lasciato in mezzo alla farina nella Scodella Per Impastare Il Pane.

Aggiungete un paio di cucchiai d’olio.

Nota importante: come detto per la farina, non mettete via l’olio, ve ne servirà ancora un goccio più avanti.

Iniziate a impastare. Personalmente vi consiglio di farlo con una sola mano a questo punto, perché l’altra potrà servirvi per aggiungere farina, se necessario, e pulire le mani sporche di impasto è piuttosto complicato.

L’impasto sarà inizialmente molto appiccicaticcio, ma si asciugherà rapidamente, man mano che assorbe tutta la farina. Se rimane troppo appiccicoso, aggiungete poca farina per volta mentre continuate a impastare.

Dopo un paio di minuti l’impasto dovrebbe avere la consistenza “giusta” e non dovrebbe più restare appiccicato alle mani o alla Scodella e potrete impastarlo con entrambe le mani per qualche minuto ancora, in modo che la pasta si compatti ancora meglio.

A questo punto potete coprire la Scodella con un panno e mettere l’impasto a lievitare per un’ora e mezza. Io l’ho messo nel forno (spento!).

Reimpasto e seconda lievitazione

Passata l’ora e mezza sarà il momento di recuperare l’impasto, lavorarlo ancora un po’ e prepararlo per la successiva infornatura.

Se tutto è andato bene, l’impasto dovrebbe essere raddoppiato (!) di volume. Se è rimasto più o meno uguale, potete stenderlo con un mattarello, aggiungere olio e rosmarino e fare delle gustose schiacciatine, ma il pane ve lo potete scordare.

Dovrete lavorare ancora un po’ l’impasto con entrambe le mani. Se è molto appiccicoso e difficile da staccare dalla Scodella Per Impastare Il Pane, potete aggiungere un po’ di farina per asciugarlo un po’.

Dopo averlo lavorato per qualche minuto, prendete abbastanza carta da forno da coprire il fondo della teglia, bagnatela con un po’ d’acqua (per renderla più maneggevole!) e disponetela sul fondo della teglia.

Versate un po’ d’olio d’oliva sulla carta da forno e spandetelo in uno strato sottile a coprire tutta l’area (potete aiutarvi con un pennellino o usare direttamente le dita).

Togliete l’impasto dalla Scodella Per Impastare Il Pane e disponetelo sulla teglia nel formato che vi è più congeniale… personalmente trovo che con questa quantità di farina abbia senso fare due o quattro “panetti”, tagliando l’impasto e facendoci delle palline. È possibile anche lasciare l’impasto tutto insieme per fare un singolo “pane”, ma ho trovato che è più difficile da cuocere correttamente.

Una volta posizionati i panetti sulla teglia, ricopritela e lasciateli ancora in forno solo con la luce accesa a lievitare per un’altra ora e mezza.

Infornare!

Terminata la seconda fase di lievitazione, togliete l’impasto dal forno e accendete quest’ultimo a 240° (o al massimo se il vostro a 240° non arriva).

Mentre aspettate che il forno si scaldi, scoprite l’impasto, che sarà di nuovo cresciuto, e praticate con un coltello alcuni tagli nella parte superiore (ad esempio il classico taglio “a croce”), che permetteranno al pane di crescere ancora durante la cottura senza rompersi.

Appena il forno è caldo, infornate!

Per la cottura, io mi sto trovando bene con 15 minuti a 230° (il massimo per il mio forno), per fare una crosta bella croccante e poi altri 20-25 minuti a 200°.

Al momento di sfornare potrete usare il panno di cui sopra per maneggiare il pane bollente e metterlo a riposare finché non raggiunge una temperatura adatta al palato :)

Riferimenti

How to quickly install and configure Splunk Enterprise

As you may have noticed, I’m not a huge fan of proprietary, closed source software. And of course I ended up having to install Splunk for a client. So here’s a few notes on what I did to get it working.

I started following this guide with a few integrations here and there.

Install the Splunk Server

First thing, you need to download the server. You have to register for it (proprietary software).

I got the 64bit RPM for my CentOS 7 server and installed it with

yum install splunk-*-linux-2.6-x86_64.rpm
/opt/splunk/bin/splunk --answer-yes --no-prompt --accept-license enable boot-start
/opt/splunk/bin/splunk --answer-yes --no-prompt --accept-license start

This will automatically accept the license and setup the Splunk Server to start at boot time.

If everything worked correctly, you should be able to connect to your Splunk Server on:

url: http://your-server-name-or-ip:8000
user: admin
pass: changeme

If it doesn’t work, check if you have a firewall on your server machine and open port tcp/8000 if needed.

For more information on this step, I’ll referr you to the Fine Manual:

Configure the Splunk Server

The logical next step is to configure the Splunk Server to listen for incoming logs.

Assuming you didn’t change (yet) your Splunk Server user and password, you’ll need to run:

/opt/splunk/bin/splunk enable listen 9997 -auth admin:changeme
/opt/splunk/bin/splunk enable deploy-server -auth admin:changeme

For more information on this step, check:

Install the Splunk Universal Forwarder on clients

Now that the server side is configured, we need to setup a client to send some logs to it. Again, head off to the download page and grab the package you need.

For large scale deployment you might want to read about how to use user-seed.conf, so you can pre-seed your installation user and password. For this quick tutorial, we’ll skip that and run directly these commands:

yum -y install splunkforwarder-*-linux-2.6-x86_64.rpm
/opt/splunkforwarder/bin/splunk --answer-yes --no-prompt --accept-license enable boot-start
/opt/splunkforwarder/bin/splunk --answer-yes --no-prompt --accept-license start

Again, this will automatically accept the license and enable the forwarder at boot time.

For more information about this step:

Configure the Universal Forwarder

Once the forwarder is installed, you’ll need to configure it to talk to your server.

Please note that the user and password I’m using are those of the local splunk, not the Splunk Server.

/opt/splunkforwarder/bin/splunk add forward-server splunk-server:9997 -auth admin:changeme
/opt/splunkforwarder/bin/splunk set deploy-poll splunk-server:8089 -auth admin:changeme
/opt/splunkforwarder/bin/splunk enable deploy-client -auth admin:changeme
/opt/splunkforwarder/bin/splunk add monitor /var/log/nginx/error.log
/opt/splunkforwarder/bin/splunk restart

In my case I added /var/log/nginx/error.log to the files that will be monitored and sent to the server.

For more information about this step, check out:

Accessing your logs on the Splunk Server

At this point you should be able to log in your Splunk Server web interface, head to the “Search & Reporting” app, and search for your data, for example I used a simple query:

source="/var/log/web/nginx/error.log"

to make sure the data from my log files was ending up in Splunk.

Workaround for NFS share not mounted at boot

I had a couple of servers unable to mount a NFS share at boot time. My /etc/fstab was something like:

[... usual stuff ...]
nfs.domain.tld:/nfs /nfs  nfs4  _netdev,auto,rw,noexec,nodev,timeo=5,retry=5,retrans=5,rsize=32768,wsize=32768,proto=tcp,hard,intr  1 2

If I tried to mount it after boot, it would work without any problem.

After checking the basic stuff (services, network access, etc), I went to see if The Internet[tm] knew any better, and this suggestion was spot-on, for some reason that I couldn’t pinpoint, even if the mountpoint definition had a _netdev attribute, it seemed like the mount was failing to properly resolve the name during the boot.

For the moment I went for a quick workaround, there was two main options: either add the NFS server hostname to /etc/hosts, or switch to the IP address in /etc/fstab. I went for the latter because it’s simpler (less stuff can break), until I can find out why it doesn’t resolve the names during the boot.

How to exclude everything except a specific pattern with rsync

Just a quick tip (and reminder for me): if you want to rsync only a specific file, or pattern, and exclude everything else, the syntax is:

rsync -a --include="*/" --include="*your-pattern*" --exclude="*" /source/path/ /destination/path/

In my specific case I wanted to copy only gzipped files, and my command line was:

rsync -avP --include="*/" --include="*your-pattern*" --exclude="*" /source/path/ /destination/path/

The first --include directive allows rsync to descend into subdirectories, while the second provides the actual filename or pattern we want to access.