Персональна освітня сорінка
by Pavlo Shcherbukha
Це продовження записок початківця про python flask. В попереніх серіях було:
В цьому блозі:
В попередній серії Python - flask запуск в контейнері від RadHat UBI8 було показано, що в Linux контейнері під Gunicorn додаток запускається в кілька потоків. Тепер виникла інша проблема, як управляти всіма цими потоками. Ну, уявимо собі, що на сервіс приходить якась команда і всі потоки повинні виконати її. Що це може бути за команда:
Для організації всього цього було прийняте рішення використати Publish/Subscribe схему на базі in-memory DB Redis. Якщо можна так сказати, то для Cloud Native applicatoin Redis виконує функцію пам’яті для public та shared змінних. Тому в цьому блозі:
Демонстраційний приклад знаходиться в репозиторії за лінком Flask app Rest API та взаємодія з Redis
Перш, ніж інтегрувати Python application з Redis потрібно навчитися впевнено розгортати сам redis. А, якщо зважити на те, що потрібно запустити як мінімум 2 сервіси: python service і БД redis - то краще запускати все це в контейнерах за допомогою docker-compolse, якщо запускати на вланому laptop.
Для прикладів вибрана redis-5 з офіційного образу redis на Docker hub з тегом версії redis:5.0.14-alpine . Для старту достатньо підготувати docker-composer.yaml
version: '3.8'
services:
redisserver:
image: redis:5.0.14-alpine
restart: always
ports:
- '6379:6379'
command: redis-server --save 20 1 --loglevel warning --requirepass 22
В цьому фрагмені Redis стартує з авторизацією по паролю [–requirepass 22]. Для запуску, потрібно перейти в каталог з файлом docker-composer.yaml та і запустити старт командою
docker compose up
На екарні отримаєте щось схоже на таке.

Якщо подивитися на контейнери, що стартонули, то можна побачити redis:
В іншій cmd сесії запускаємо:
docker ps
На екарні отримаєте щось схоже на таке.

Тепер спробуємо підключитися до контейнера з допомогою redis-cli. Тут ми нічого не інсталюємо на робочу станцію, а заходиво в середину контейнера, шляхом запука redis-cli:
docker exec -u root -it 1c78b4161df0 redis-cli -a 22
де 1c78b4161df0 - ідентифікатор контейнера.
Тобто, тут ми заходимо по ssh як root в контейнер з id 1c78b4161df, запускаючи redis-cli з авторизацією, вказуючи пароль до БД з ключем -a
На екрані отримаєте щось схоже на таке.

Ну і для перевірки з’єднання задаємо комнаду “PING”, а у відповідь отримуємо PONG. Таким чином в мінімальному вигляді контейнре запущено.
Особисто я Redis-cli не користуюся. Але в цьому розділі хочу паказати деякі можливості redis, які в майбутньому буду викистовувати через спеціальні мовні бібіліотеки
Найпершою і найпростішою операцією є запам’ятати пари: ключ-знаення. Цікавим варіаном є можливість зберігання ключа заданий період часу (секунд, мілісекунд).
set myKey myvaluse
get myKey

Встановити ключ, що “живе” протягом визначеного часу. На прикладі, що показано, видно, як встановлено значення ключа на 4 секунди. А потім, через якийсь час - ключ “пропадає”
#
# set myKey value [expiration EX seconds|PX milliseconds] [NX|XX]
set myKey XCODE EX 4

Тут показано простий варіант установки лічильника і його постійного збільшення
127.0.0.1:6379> set xcntr 0
OK
127.0.0.1:6379> get xcntr
"0"
127.0.0.1:6379> incr xcntr
(integer) 1
127.0.0.1:6379> incr xcntr
(integer) 2
127.0.0.1:6379> incr xcntr
(integer) 3
127.0.0.1:6379> incr xcntr
(integer) 4
127.0.0.1:6379> incr xcntr
(integer) 5
127.0.0.1:6379> get xcntr

Отримати всі ключі в БД можа командою
# keys pattern
На приклад:
127.0.0.1:6379> keys *
1) "jsondata"
2) "myhash"
3) "xcntr"
4) "counter"
5) "shkey1"
Ця команда під одним ключем може зберігати кілька значень FIELD: VALUE. Обмеження - прочитатит можна тілько по одному полю. Збережемо під ключем sh-book опис книжки: назву, автора, рік публікації. В принципі, це аналогічно збереженню JSON
{
“sh-book”: {“title”: “”, “author”: “”, published: 2009} }
hset sh-book title "The Museum of Abandoned Secrets"
hset sh-book author "Oksana Zabuzhko"
hset sh-book published "2009"
або ж можна ввести групову команду
hset sh-book title "Second Attempt" author "Oksana Zabuzhko" published "2005"
hexists sh-book title
hdel sh-book title
По суті ми зберегли та к би мовити плоский json
del shkey
127.0.0.1:6379> keys *
1) "myhash"
2) "xcntr"
3) "counter"
4) "shhkey2"
5) "gey"
6) "shkey1"
7) "jsondata"
8) "get"
9) "sh-book"
10) "shhkey"
127.0.0.1:6379> del shhkey
(integer) 1
127.0.0.1:6379> keys *
1) "myhash"
2) "xcntr"
3) "counter"
4) "shhkey2"
5) "gey"
6) "shkey1"
7) "jsondata"
8) "get"
9) "sh-book"
127.0.0.1:6379>
Так можна перевірити наявнісмть ключа
127.0.0.1:6379> keys *
1) "myhash"
2) "xcntr"
3) "counter"
4) "shhkey2"
5) "gey"
6) "shkey1"
7) "jsondata"
8) "get"
9) "sh-book"
127.0.0.1:6379> exists sh-book
(integer) 1
127.0.0.1:6379>
Допустимо у нас є JSON:
{ “title”: “Second Attempt” , “author”: “Oksana Zabuzhko”, “published”: 2005 }
то зберегти його можна звичайною командою set Зберегти його можна звичайною командою set
set book1 '{ "title": "Second Attempt" , "author": "Oksana Zabuzhko", "published": 2005 }'
Ось результат роботи комнади:
127.0.0.1:6379> set book1 '{ "title": "Second Attempt" , "author": "Oksana Zabuzhko", "published": 2005 }'
OK
127.0.0.1:6379> get book1
"{ \"title\": \"Second Attempt\" , \"author\": \"Oksana Zabuzhko\", \"published\": 2005 }"
127.0.0.1:6379>
В цьому розділі описана проста демка, для демонстрації того, як використати redis сумісно з python flask applicatoin. Програмний код демки можна знайти за лінком Flask app Rest API та взаємодія з Redis. Опис бібліотеки Python для роботи з redis знаходиться за лінком: redis 4.3.4 або ж прямо на github redis-py. Цікаві і потрібні, як на мій погляд, приклади наедені в ndexing / querying JSON documents
В демці розглядається, як запустити redis та Python web service, що зверта ться до redis на совєму ноутбуці в контейнерах, викорситовуючи docker-composer. Це, так би мовити, створення та запуск середовища розробки на своєму laptop.
Redis використовується в модулі views.py. Підключення до redis описано у наведеному фрагменті:
log("Підключення до Redis")
irds_host = os.getenv('RDS_HOST');
irds_port = os.getenv('RDS_PORT');
irds_psw = os.getenv('RDS_PSW');
log('Підключеня до redis: ' + 'host=' + irds_host )
log('Підключеня до redis: ' + 'Порт=' + str(irds_port) )
log('Підключеня до redis: ' + 'Пароль: ' + irds_psw )
red = redis.StrictRedis(irds_host, irds_port, charset="utf-8", password=irds_psw, decode_responses=True)
log(" Trying PING")
log("1=======================")
rping=red.ping()
log( str(rping) )
if rping:
log("redis Connected")
#sub = red.pubsub()
#sub.subscribe( ichannel )
else:
log("redis NOT CONNECTED!!!")
log("2=======================")
Як видно, парамери підключення параметризуються в ENV-variables сервісу: RDS_HOST, RDS_PORT, RDS_PSW, які вичитуються при старті сервісу і повинні бути задані при старті контейнера (якщо сервіс стартує в контейнері). Підклюення (чи то створення об’єкту підклченя) описано в команді: red = redis.StrictRedis ….. А ось перевырити можливість взаємодії сервіса та redis можна з допопогою команди PING:
rping=red.ping()
Тобто, якщо rping=True, значить до redis сервіс підключився.

Використаемо просут команду бібліотеки: incrby(name, amount=1). Також, при старті сервісу ключ потрібно створити, а потім бажано ще і прочитати. Для цього використаємо комнади:
Цю задачу вирішують два простих фагменти кода.
Тут при старті створюється ключ в БД
i_apicntr_key="APICALLS"
if rping:
log("redis Connected")
log("set predefined key by 0 value: " + i_apicntr_key )
red.set(i_apicntr_key, 0)
log("Check the valuse of key: " + i_apicntr_key )
log( "Read value: " + str( red.get(i_apicntr_key) ) )
А тут є функція, що виконє інкремент значення ключа
#==================================================
# Функція підраховування викликів API
#
#=================================================
def apicallscntr():
l_label="apicallscntr"
log("Старт", l_label)
return red.incrby( i_apicntr_key, 1)
Ну і далі виклик цієї функції вмонтовуємо в оброники викликів API
Для читання ключів використовується функціія: keys(pattern=’*’, **kwargs).
У відповіді повинні отримати масив всіх існуючих ключів, типу такого:
{
"list": ["shhkey2", "shkey1", "APICALLS", "book1", "myhash", "jsondata", "get", "counter", "xcntr", "sh-book", "gey"]
}
Ну а для створення ключа використовуєму функцію: - set(name, value, ex=None, px=None, nx=False, xx=False, keepttl=False, get=False, exat=None, pxat=None)
Тіло POST запиту:
{
"keyname": "test1",
"keyvalue": "test1 value"
}
Для демонстрації взаємодії з redis цього досить. Тепер проблема, як це запустити у себе на машині.
Коли програмний код уже існує, потрібно налаштувати узгожений запуск двох контейнерів:
контейнера redis
контейнера з PYthon додатком на laptop
Для цього використано docker-composer. Хоч, выдверто кажучи, я його ы не дуже люблю. Мені більш звичним є такі платформи як Openshift та kubernetes. Але для цього випадку прийшлося використати docker-composer. Основним конфігураційним файлом, що зв’язує різні контейнери між собою є yaml-файл: docker-compose.yaml:
version: '3.8'
services:
redisserver:
image: redis:5.0.14-alpine
restart: always
ports:
- '6379:6379'
command: redis-server --save 20 1 --loglevel warning --requirepass 22
smplapp-srvc-redis:
build:
context: ./
dockerfile: Dockerfile_local
ports:
- "8081:8080"
links:
- "redisserver:redis"
environment:
GUNICORN_CMD_ARGS: "--workers=1 --bind=0.0.0.0:8080 --access-logfile=-"
APP_MODULE: "hello_app.webapp"
RDS_HOST: "redisserver"
RDS_PORT: 6379
RDS_PSW: "22"
На pic-08 показано з яких частин цей файл склажається:

На pic-09 показані основні конфігураційні елементи, пов’язані з конфігуруванням запуску redis

Кореневий елемен окрем взятого сервісу, що параметризуємо. В подальшому на нього можна буде посилатися.
Вказує на образ, з якого буде створюватися та стартувати контейнер.
Вказує на визначення портів в форматі [локальний порт вашого laptop]:[порт контейнера].
Вказує на команду, що перекриває комнаду Dockerfile CMD при старті контейнера. В даному випадку ми додаємо додаткові параметри при старті сервісу БД.
На pic-10 показані основні конфігураційні елементи, пов’язані з конфігуруванням запуску контейнера додатку на Python

Кореневий елемен окрем взятого сервісу, що параметризуємо. В подальшому на нього можна буде посилатися.
Вказує на Dockerfile, з якого буде створюватися образ та стартувати контейнер. При цьому, context вказує на path до Dockerfile . - значить поточний каталог, а dockerfile вказує на найменування Dockerfile в якому описані правила побудови образу.
Вказує на визначення портів в форматі [локальний порт вашого laptop]:[порт контейнера].
Вказує на блок links, що так би мовити зв’язує два контейнера між собою так, що вони можуть між собою взаємодіяти по tcp/ip. В даному випадку вказано, що сервіс smplapp-srvc-redis може звертатися до сервісу з назвою хоста “redisserver”. :redis це альтернативна назва.
Вказує на блок environments змінних які потрібно задати для старту сервісу. Цифрою 6 показано, як сконфігуровано підключення до redis.
Запукаються командою
docker compose up
Але мені потрібно, щоб при запуску сервіс smplapp-srvc-redis перебудовував образ, бо я в процесі розробки можу міняти програмний код. Для цього використовується команда: –build smplapp-srvc-redis, що запускає перебудову образа
docker compose up --build smplapp-srvc-redis
Зупинити сервыс можна по CTRL + C або просто:
docker compose stop