No nem arra gondolok, hogy költözzünk ki egy kényelmesen berendezett, jól felszerelt konténerházba. Bár lehet, hogy az is segít, de maradjunk a Docker-nél (azon belül is a Docker Compose nevezetű eszköznél) és annál, hogy hogyan tudja segíteni a fejlesztő-, teszt- és build környezetek létrehozását. Kezdjük is valami egyszerűvel.

Hello Compose

Tegyül fel, hogy van egy kis PHP-s alkalmazásunk, mindenféle függőség nélkül:

www/index.php
<?php
echo "Hello World";

Szó, ami szó, nem vittük túlzásba a dolgot. A hozzá tartozó Docker Compose konfiguráció valahogy így nézhetne ki hozzá:

docker-compose.yml
version: "3"
services:
  app:
    image: php:7.1-alpine
    ports:
      - 8080:8080
    command: php -S 0.0.0.0:8080 -t /app/www
    volumes:
      - .:/app

Ezek után, ha egy docker-compose up parancsot kiadunk a megfelelő könyvtárban, elindul a beépített PHP-s webszerver a 8080-as porton és a http://127.0.0.1:8080/ oldalra rápillantva láthatjuk is az ismerős szöveget.

Létezik pl. Apache vagy FPM alapú csomag is, de ha nincsenek nagy igényeink, a beépített webszerver használata talán a legegyszerűbb megoldás.

Hello Composer

Ez mind szép és jó, de nagy rá az esély, hogy szükségünk van valamilyen külső csomagra, amit Composer-rel szeretnénk telepíteni. Ennek a megvalósítására több lehetőségünk is van. Használhatunk például egy olyan Docker image-et, ami tartalmazza a composer parancsot és telepíthetjük a függőségeket a konténeren belül:

docker-compose.yml
version: "3"
services:
  app:
    image: composer:1.3
    volumes:
      - .:/app

Telepíteni pedig a következő paranccsal tudunk:

$ docker-compose run --rm app composer require phpunit/phpunit

A megoldás szépséghibája, hogy a composer által generált fájlok root felhasználóval jönnek létre a host gépen. Ez nem feltétlen kell, hogy zavarjon minket, mivel írni csak a konténeren keresztül fogunk oda, olvasni pedig bárki tudja így is.

Szükség esetén elpakolhatjuk a generált fájlokat egy Docker volume-ra, így csak egy vendor könyvtárunk lesz root-tal, ami üres.

docker-compose.yml
version: "3"
volumes:
  vendor-files:
services:
  app:
    image: composer:1.3
    volumes:
      - .:/app
      - vendor-files:/app/vendor

Már el is hangzott a megoldás fő problémája, a vendor könyvtár üres, a host gép nem látja a generált fájlokat, így például nem tudjuk igénybe venni ezekre az IDE kódkiegészítő funkcióit se.

Lehet persze a composer parancsot simán Docker-en kívül is futtatni, így megfelelő felhasználóval jönnek létre a fájlok és az IDE is látja őket. Ebben az esetben viszont a fejlesztők gépein és a build gépeken is ott kell lennie a telepítéshez szükséges eszközöknek.

Hello MySQL

Feltelepítettük a kedvenc keretrendszerünket, ideje beszerezni egy adatbázist is hozzá. Először is építünk egy olyan PHP-s image-et, ami tartalmazza a megfelelő kiterjesztéseket:

Dockerfile
FROM php:7.1-alpine

RUN docker-php-ext-install pdo pdo_mysql

A konténerek alapesetben úgy működnek, hogy a bennük történt változások nem nevezhetőek maradandónak, ami esetünkben azt jelenti, hogy minden docker-compose up hívásnál egy üres adatbázist kapunk. Ez felhasználástól függően akár még elvárt működés is lehet, de ha tartósabb megoldást szeretnénk, a volume-ok ebben is segítenek.

docker-compose.yml
version: "3"
volumes:
  database-data:
services:
  app:
    build: .
    ports:
      - 8080:8080
    depends_on:
      - database
    command: php -S 0.0.0.0:8080 -t /app/www
    volumes:
      - .:/app
  database:
    image: mysql:5.7
    volumes:
      - database-data:/data
    environment:
      MYSQL_ROOT_PASSWORD: test
      MYSQL_DATABASE: test
      MYSQL_USER: test
      MYSQL_PASSWORD: test

Érdemes megfigyelni a depends_on részt, amitől a database konténer előbb fog elindulni, mint az app. Itt érdemes megjegyezni, hogy csak előbb indul, nem várja azt meg, hogy be is fejezze az elindulását, így ha az app elég gyors, előállhat olyan helyzet, hogy még nem éri el az adatbázist. Ennek kivédésére léteznek megoldások, de ezt meghagyom házi feladatnak.

A másik érdekesség a környezeti változók a database konténer beállításainál. Az image-eknek egész sok beállítása lehet (pl. a mysql image esetén megadhatunk fájlokat is, amikből inicializálódik az adatbázis) és szolgáltathatnak extra parancsokat, hogy könnyebb legyen rájuk saját image-et építeni (mint pl. a php-s image docker-php-ext-install parancsa). Ezekről az image Docker Hub-os oldalán vagy közvetlenül a Docker image forrásából is tájékozódhatunk.

A konténerek között a Docker saját hálózatot épít ki, aminek segítségével a PHP-s kódból egész egyszerűen elérhetjük az adatbázist (a docker-compose.yml fájlban szereplő service név alapján):

www/index.php
<?php

$db = new PDO('mysql:host=database;dbname=test', 'test', 'test');
$stmt = $db->query('SHOW VARIABLES LIKE "%version%"');

var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));

Első körben ennyi lett volna. A következő részben egy kicsivel bonyolultabb példákkal folytatjuk.

Nagyjából két éve kezdtem el írni egy statikus blog generátort, csak úgy a saját magam szórakoztatására. Közel egy éve többé-kevésbé el is készült a frissített blog design-nal együtt, de valahogy sose jutottam el odáig, hogy lecseréljem a WordPress-es megoldást rá.

Aztán a héten törént egy váratlan esemény. Valószínűleg egy WordPress sebezhetőséget kihasználva elkezdtek lecserélődni meglévő bejegyzések az oldalon. Ennek a problémának a gyors megoldása pedig az lett, hogy leváltottam a jó öreg WordPress-t az új, még nem 100%-osan kész statikus változatra. További érdekesség, hogy az oldal alcímét (system failure) nem az esemény ihlette, hanem jóval korábban született. :)

Már egy ideje az íróasztal fiókom mélyén tanyázik egy MCP23017 chip, amivel például a Raspberry Pi GPIO portjait lehet bővíteni, szám szerint 16 darab új csatlakozással. A 16 darab csatlakozás pedig pont elég arra, hogy 16 darab LED segítségével megjelenítsünk egy bináris órát (bár sajnos csak 12 órás megjelenítéssel).

Hardver és összedrótozás

Először is talán ismerkedjünk meg egy kicsit közelebbről az MCP23017-tel. I²C buszon lehet vele kommunikálni, a hardver címét az A0-2 lábak határozzák meg.

A GPA0-7 és GPB0-7 lábakra fogjuk rákötni a ledeket (esetleg a LED-hez kellhet még egy ellenállás is, de én attól eltekintettem), A VSS-re és az A0-2 lábakra megy a föld (ez a 0x20-as hardvercímet fogja eredményezni), a VDD-re és a RESET-re a 3.3V, az SLC és SDA pedig a Raspberry Pi azonos nevű kivezetéseivel lesz összekötve.

Katt a nagyobb változatért.

Bónuszként a kábelrengeteg miatt még úgy is fog tűnni, mintha valami bonyolult dolog lenne. Ezen kívül, ami még érdekel minket, azok az MCP23017 regiszterei és azok címei. Legalábbis egy részük:

0x00 IODIRA A GPA0-7 lábak inputként vagy outputként működjenek
0x01 IODIRB A GPB0-7 lábak inputként vagy outputként működjenek
0x12 GPIOA Ennek a segítségével lehet a GPA0-7 lábakat elérni
0x13 GPIOB Ennek a segítségével lehet a GPB0-7 lábakat elérni

A chip dokumentációjában utána lehet nézni a többinek is, ha valakit esetleg részletesebben is érdekelne.

Konfigurálás

Ennyit a hardverről, végre rátérhetünk a dolog szoftveres részére. Mint azt már fentebb említettem, a chip I²C-t használ, ami alapból nincsen bekapcsolva az RPi-n. Először is a /etc/modules fájlba kell két új sor, mondjuk a végére:

i2c-bcm2708
i2c-dev

Aztán a /etc/modprobe.d/raspi-blacklist.conf fájlban a blacklist i2c-bcm2708 sort kell törölni (vagy kikommentelni). Szükség van ezen kívül még néhány csomagra telepítésére is:

$ sudo apt-get install python-smbus i2c-tools

Ha ez is végzett, akkor már csak egy újraindítás, és el is kezdhetjük használatba venni a szerkezetet.

Életre keltés

Először is szükségünk lesz a chip címére, amit az i2cdetect parancs segítségével tudunk kideríteni. A fenti drótozással elvileg ezt kell kapnunk:

$ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

Amiből kiderül, hogy 0x20 az eszköz címe (régebbi RPi-n előfordulhat, hogy sudo i2cdetect -y 0 a nyerő parancs), úgyhogy rá is térhetünk a kódra.

import time
import smbus

Az már a konfigurálás során kiderült, hogy a Python smbus modulját fogjuk használni (meg egy kis időt, ha már egyszer órát csinálunk).

i2c = 1
addr = 0x20

Néhány beállítás: az i2c értéke ugyanaz, mint amit az i2cdetect parancsban is használtunk -y paraméterként, az addr értéke pedig az i2cdetect válaszából kinyert hardver cím.

bus = smbus.SMBus(i2c)
bus.write_byte_data(addr, 0x00, 0x00)
bus.write_byte_data(addr, 0x01, 0x00)

Kezdődhet a kommunikáció. A chip 0x00-s és 0x01-es regisztereit is kinullázzuk, ami mindkét nyolcas csatlakozó csoportot output-ra állítja.

try:
  while True:
    now = time.localtime()
    h = now.tm_hour
    if h > 12:
      h = h - 12
    
    x = '{:04b}{:06b}{:06b}'.format(h, now.tm_min, now.tm_sec)
    
    bus.write_byte_data(addr, 0x12, int(x[8:], 2))
    bus.write_byte_data(addr, 0x13, int(x[:8], 2))
    time.sleep(0.01)
except KeyboardInterrupt:
    bus.write_byte_data(addr, 0x12, 0x00)
    bus.write_byte_data(addr, 0x13, 0x00)
    print

Először átvarázsoljuk az aktuális időt egy nekünk megfelelő bináris formátumba, aztán beírjuk az egyes byte-okat a 0x12-es és 0x13-as regiszterekbe. Aztán ez megy tovább a végtelenségig. Vagyis amíg Ctrl+C-t nem nyomunk, akkor kinullázzuk mindkét regisztert, ami az összes ledet lekapcsolja. A végeredmény pedig:

Korábban már volt róla szó, hogy hogyan lehetne közvetlenül Sublime-ból build-eket futtatni a virtuális gépünkön egy plugin és egy webalkalmazás segítségével. Most nézzünk meg egy másik megközelítést, ami a Sublime beépített build rendszer támogatását és a PuTTY parancssoros változatát használja.

Amire szükségünk lesz (Windows-os környezetben):

Linux-os környezetben a Plink helyett az ssh, PuTTYgen helyett pedig az ssh-keygen használható.

Elkészítés

Első lépésként, ha még nincs beállítva publikus kulcs autentikáció a hozzáférésünkhöz a szerveren, akkor indítsuk el a PuTTYgen-t és generáljunk egy kulcsot:

A bejegyzés írása során többször próbáltam kattintani, hogy eltűnjön a kijelölés.

Ahogy azt a PuTTYgen okosan írja is, a Generate gomb megnyomása után kijelölt szöveget be kell másolnunk a szerveren a ~/.ssh/authorized_keys fájlba. Aztán passphrase nélkül mentsük el a privát kulcsot, arra még a későbbiekben szükségünk lesz.

Jöhet a Sublime beállítása. Feltételezem, hogy éppen egy projektben vagyunk, ha nem, csináljunk egyet gyorsan. Ez után a Project > Edit Project menüre kattintva a megnyíló config fájlhoz hozzáadjuk a saját build rendszerünket (a nagy valószínűséggel már ott lévő "folders" kulccsal azonos szintre). Majd a Tools > Build Systems menüpontban ki is választjuk, hogy azt használja a Sublime.

"build_systems":
[
    {
        "name": "csudiszuper build rendszer",
        "working_dir": "c:\\path\\to\\plink\\",
        "cmd": [
            "plink",
            "-i", "c:\\path\\to\\private_key.ppk",
            "username@virtual.machine.host",
            "futtatandó parancsok"
        ]
    }
]

A "futtatandó parancsok" rész a fenti configban nagyban függ attól, hogy mit is szeretnénk pontosan build futtatása címszó alatt csinálni, de nagyjából annyiból áll, hogy belépünk a projektünk könyvtárába és meghívjuk a build rendszerünket (Phing, Grunt, Ant, make vagy bármi más, ami szimpatikus).
Ezek után a Ctrl+B billentyűkombinációval el is indíthatjuk a futtatást és láthatjuk a szerkesztőben a kimenetét.

A példa kedvéért a guzzle make test-jét futtattam. Katt a nagyobb változatért.

Ezen a ponton még belefuthatunk néhány hibába. Például, hogy ha még nem csatlakoztunk a szerverhez, akkor el kellene fogadnunk az RSA kulcsát, amit a build rendszeren keresztül nem tudunk megtenni, így érdemes pl. PuTTY-ból megejteni az első kapcsolódást. Egy másik gyakori probléma az lehet, hogy visszautasítja a szerver a kulcsot, aminek például az SSH szerver beállításaihoz (PubkeyAuthentication rész a /etc/ssh/sshd_config fájlban) vagy a home könyvtárunkban lévő .ssh könyvtár és a benne lévő fájlok jogosultságaihoz (minden csak a saját felhasználónk által legyen írható/olvasható) lehet köze.

Én egyébként mostanában hajlok arra, hogy make-et használjak build-elésre. Jó eséllyel nem kell hozzá semmit se telepíteni és a Phing-et elég lassúnak találtam, ami gyakori futtatás esetén nem egy vágyott tulajdonság. Mivel egyébként is csak konzolos parancsokat futtatok, egy egész egyszerű Makefile-lal meg lehet úszni például a coding standard-ek ellenőrzését és a unit tesztek futtatását:

PHPUNIT=@vendor/bin/phpunit
PHPCS=@vendor/bin/phpcs

all: test checkstyle

test:
  $(PHPUNIT) --bootstrap=vendor/autoload.php tests/

checkstyle:
    $(PHPCS) --report=full --standard=PSR2 src/ tests/

Már egy ideje beüzemeltem otthonra egy Raspberry Pi-t, de eddig nem használtam túl sok mindenre. A kamera modul megjelenésével gondoltam ideje lenne kezdeni vele valamit... Egy elég egyszerű dologról van szó, a kamera modullal való ismerkedéshez tökéletes kis projekt lehet. A beépített raspistill parancs támogatja is a time-lapse képek készítését a -tl és -t kapcsolók segítségével, de úgy vettem észre, hogy hosszabb futás esetén eléggé megakasztja a rendszert, így célravezetőbb lehet egy kis shell scriptet használni (nevezzük mondjuk timelapse.sh-nak), ami megfelelő időközönként futtatja a raspistill parancsot:

#!/bin/bash

for i in $(seq -f "%04g" 1 1440)
do
    raspistill -o "/path/to/images/IMG_${i}.jpg" -t 1000
    sleep 59
done

Ez a kód így nagyjából percenként fog egy képet csinálni, 1440-szer (tehát kb. egy napig fog futni). A raspistill paraméterezésével még érdemes lehet egy kicsit foglalkozni (például a képméretet beállítani a -w és -h kapcsolók segítségével). A sleep helyett használhatunk usleep-et is, ha pontosabban szeretnénk megadni a várakozási időt két futás között. Futtatni a következő módon ajánlott:

$ ./timelapse.sh &

Így háttérbe küldjük a folyamatot és nyugodtan kiléphetünk a shell-ből, a script tovább fog futni. Ha elkészültek a képek már csak össze kell őket fűzni egy videóvá. Ezt érdemesebb egy erősebb hardveren megtenni, ha nem akarunk a szükségesnél jóval többet várni. Parancssorból használhatjuk például az avconv parancsot:

$ avconv \
    -f image2 \
    -i /path/to/images/IMG_%04d.jpg \
    -r 24 \
    -qscale 2 \
    -s 640x480 \
    /path/to/output.avi

A különböző paraméterekkel itt is érdemes lehet eljátszogatni, hogy olyan kimenetet kapjunk, amit szeretnénk. Nálam ilyesmi lett a végeredmény egy délután alatt, percenként kb. 6 képet készítve:

Összes bejegyzés