Блоґ одного кібера

Історія хвороби контуженого інформаційним вибухом

Archive for the ‘Кодерство’ Category

Як написати собі маленький Google Translate на Grammatical Framework за 15 хв

with 3 comments

І з якістю трохи кращою ніж в Google Translate, але з обсягом можливих перекладів набагато меншим.

Що я роблю на роботі

Зараз я працюю в Zalando – компанії яка продає різний крам в купу країн Європи. Відповідно, треба підтримувати сайт для багатьох країн. Я працюю у відділі який займається інструментами що допомагають створювати наповнення сайту. Процес додавання нового вмісту на сайт дуже трудомісткий, бо потрібно сфотографувати продукт, на столі, на моделі, перевірити розміри, матеріали і інструкції з догляду, написати опис, заголовок і т.п.. А потім ще перекласти це на 8 мов і кілька локалей, бо деякі країни DACH (німецькомовний простір) не входять до єврозони і використовують швейцарські франки наприклад. Це велика купа часу, і великий простір для автоматизації.

Google Translate не підходить, тому що наприклад ми продаємо скатертини, і німецький менеджер пише “Tischdecken” (множина від Tischdecke), Google Translate думає що це дієслово (бо є таке дієслово), і перекладає “Set the table” (накривайте столи!).

Насправді задача стоїть простіша ніж повний машинний переклад, бо дані отримуються не якоюсь конкретною мовою, а набором параметрів – “що”, “коли”, “яке”, “для кого”, “по чому” і т.п.. Тобто якщо я отримую що=взуття, яке=класичне коли=зима для кого=хлопчики, по чому=<100 € то я (в сенсі моя програма), пише:

'de_CH': 'Klassische Winterschuhe für Jungs - unter CHF 107',
'de_DE': 'Klassische Winterschuhe für Jungs - unter 100 €',
'en_GB': 'Classic Winter Shoes for Boys - under £85',

Все просто – перекладаємо слова за словником і підставляємо в шаблон для конкретного набору компонентів. З німецькою лише один виклик – з’єднання слів. Тобто якщо я з’єдную Winter з чимось – просто конкатеную, а от коли Fruhling – додаю між Fruhling і тим словом яке приєдную ще “s”. Є ще інші правила.

Складність з’являється в слов’янських мовах. Там є відмінювання прикметників у множині.

На роботі я багато думаю як би то не робити роботу яку вже хтось зробив за мене

Програмісти ліниві створіння. Нащо писати код якщо код вже є написаний на StackOverflow і Github? Треба лише знати де той код лежить. І я почав собі шукати як інші люди це робили. До того як з’явилось машинне навчання і статистичні методи, всі пробували підхід на основі правил. І проміжної мови.

З проміжною мовою процес розбивався на дві частини – спершу проводився синтаксичний аналіз тексту на мові джерела, тоді з побудованого представлення тексту на проміжній мові (зазвичай дерево) будувався текст іншою мовою.

Моя задача вже простіша, бо ніякого синтаксичного аналізу робити не треба, треба просто згенерувати текст 8-ма мовами на основі структури даних. Це як два пальці відрендерити контекст за допомогою шаблону, ми веб-деви тільки таке й робимо.

Тільки виявляється що шаблон для генерації простого заголовку на зразок: “Mindestens 500 € sparen – Premium Herbstmode” або “Premium Herbstmode unter 999 €” займає мало не ввесь екран.

Я подумав що може нарешті настав той момент коли я на Кубику не даремно вчився, пам’ятаю Opa Chomsky Style, і треба застосувати якусь контекстно-вільну граматику, чи яка там наступна за потужністю, бо контекстно-вільна здається не потягне слов’янську морфологію.

І раптом в своїх пошуках надибав Grammatical Framework. Це штука яку вже більше ніж 20 років пишуть на Хаскелі, значить люди вміють точно більше ніж я. І автор приходить з доповіддю в Google, в якій кілька слайдів на початку – суцільний тролінг команди Google Translate:

Але мене підкупило речення з їх сайту, http://www.grammaticalframework.org/

GF is easy to learn by following the tutorial. You can write your first translator in 15 minutes.

До роботи!

Ну що ж, заводимо таймер, і спробуємо наприклад написати перекладач, що може перекласти набір шахматних словосполучень /(black|white) (queen|king)/ з англійської на українську. Шахмати, тому що одягом я й на роботі можу зайнятись, а блог я пишу вдома і хочеться трохи змінити контекст.

Для початку варто встановити компілятор/інтерпретатор gf. На сторінці завантаження є пакети для різних ОС, з інструкцією про те як їх поставити. Ще можна поставити плагін підсвітки синтаксису для вашого редактора, перелік є на цій сторінці.

Hello, world!

Тепер, в файлі Chess.gf пишемо таку граматику:

-- це, до речі, коментар
abstract Chess = {

  flags startcat = Piece ;

  cat PieceType ; Color ; Piece;

  fun
    piece : Color -> PieceType -> Piece ;
    Black, White : Color;
    Queen, King: PieceType;
}

abstract означає що ми описуємо граматику проміжної мови, тобто не якоїсь конкретної людської, а мови якою ми описуватимемо сенс тексту.

flags startcat = Piece ; означає що початковою категорією (коренем дерева) буде фігура.

cat перелічує можливі категорії (типи вузлів дерева).

Секція fun описує функції. Функції описують як будується дерево, і можуть бути будь-якої арності, в тому числі нулярні. Наприклад тут функції Black та White не приймають аргументів але повертають колір. (По суті – константи). Зате функція piece приймає дві категорії і повертає категорію для фігури.

Тепер про те як перетворити дерево на послідовність токенів якоюсь мовою. Це називається лінеаризацією, і описується конкретною граматикою:

concrete ChessEng of Chess = {

  lincat Piece, Color, PieceType = {s : Str} ;

  lin
    piece color type = {s = color.s ++ type.s} ;
    Black = {s = "black"} ;
    White = {s = "white"} ;
    Queen = {s = "queen"} ;
    King = {s = "king"} ;
}

Зберігаємо її в файлі ChessEng.gf. Для GF важливо щоб кожна граматика була в своєму файлі і щоб він називався так само як називається граматика, інакше він дає помилки.

lincat описує які типи токенів будуть відповідати категоріям абстрактної категорії. В нашому випадку це структура з одним полем типу Str, тому що пізніше нам знадобляться інші поля.

lin описує функції лінеаризації типи яких були описані в абстрактній граматиці. Для унарних функцій – як власне пишеться слово, для інших – як скомбінувати інші лінеаризації.

Щоб потестувати як все працює, запускаємо команду gf Chess*, яка завантажує обидві граматики і починає інтерактивну сесію.

Тут ми можемо наприклад перевіряти наші речення на правильність:

Chess> parse "black queen"
piece Black Queen

Chess> parse "snow queen"
The parser failed at token 1: "snow"

Правильно, ми тут пишемо граматику для шахмат а не для казок. Ще можна попросити згенерувати випадкове дерево:

Chess> generate_random
piece White Queen

Або його лінеаризацію:

Chess> generate_random | linearize
black queen

Або всі можливі речення:

Chess> generate_trees | l
black king
black queen
white king
white queen

Морфологія

Тепер давайте зробимо переклад на українську! Для цього треба описати граматику української. Додаємо переклад з англійської в файл ChessUkr.gf:

concrete ChessUkr of Chess = {

  lincat Piece, Color, PieceType = {s : Str} ;

  lin
    piece color type = {s = color.s ++ type.s} ;
    Black = {s = "чорний"} ;
    White = {s = "білий"} ;
    Queen = {s = "королева"} ;
    King = {s = "король"} ;
}

Завантажуємо і тестуємо перекладач в консолі:

Chess> parse -lang=ChessEng "black king" | linearize -lang=ChessUkr
чорний король

Дуже добре!

Chess> parse -lang=ChessEng "white queen" | linearize -lang=ChessUkr
білий королева

От засада! Навіть Google Translate вміє краще. Треба ввести в українську граматику поняття роду. Для цього до іменників треба додати інформацію про те якого вони роду, а кольори мають описувати як вони відмінюються залежно від роду:

concrete ChessUkr of Chess = {

  param Gender = Masc | Fem ;
  lincat Piece, PieceType = {s : Str; gender : Gender} ;
  lincat Color = {s : Gender => Str} ;

  lin
    piece color type = {s = color.s ! type.gender ++ type.s; gender=type.gender} ;
    Black = {s = table {
        Masc => "чорний";
        Fem => "чорна"
    } };
    White = {s = table {
        Masc => "білий";
        Fem => "біла"
    } };
    Queen = {s = "королева"; gender=Fem} ;
    King = {s = "король"; gender=Masc} ;
}

Тут нам і стало в нагоді те що наші категорії – це структури, а не просто рядки. Чіпляємо до кожної фігури інформацію про стать.

Gender => Str задає тип таблиці (це майже як функція, тільки описується чимось типу хеша). Кожен колір тепер – це таблиця. Операція “!” – це вибір значення з таблиці.

Тестуємо нову граматику української:

Chess> generate_trees | l
black king
чорний король
black queen
чорна королева
white king
білий король
white queen
біла королева


Chess> parse -lang=ChessUkr "білий король"
piece White King

Chess> parse -lang=ChessUkr "біла король"
The parser failed at token 2: "\1082\1086\1088\1086\1083\1100"

Хаха, він думає що ми неправильно вжили слово король, бо після біла може стояти лише королева. Але помилку знайшов!

Рефакторинг

Накодили, потестували, тепер пора зробити код гарнішим.

Писати словник в форматі:

    White = {s = table {
        Masc => "білий";
        Fem => "біла"
    } };

Коли ми можливо захочемо додати ще слів які можливо матимуть ще середній рід і множину – доволі трудозатратно. Але ми можемо написати функцію. Точніше оператор, бо в GF функцією ми вже назвали штуку яка будує дерево.

Ще нам знадобиться згадати школу, в якій нам казали що прикметники твердої групи в чоловічому роді завжди закінчуються на -ий, а в жіночому на -а.

Тоді ми можемо додати в українську граматику такий оператор:

  -- hard adjective
  oper ha: Str -> {s: Gender => Str} = \stem -> {
      s = table {
          Masc => stem+"ий";
          Fem => stem+"а"
      }
  };

Він приймає рядок і повертає токен в якого s – це таблиця що відображає рід на написання.

І тепер ми можемо швидко додавати багато прикметників:

    Black = ha "чорн";
    White = ha "біл";
    Fast = ha "швидк";
    Defenceless = ha "беззахисн";

і навіть швидко додати множину для всіх якщо додамо закінчення рід. Правда колір певне варто перейменувати в прикметник, а рід – в граматичну категорію. Але іменування – це вже складніша проблема програмування.

Тепер додайте решту слів і маєте свій перекладач!

Written by bunyk

2 Лютого, 2020 at 19:14

Опубліковано в Кодерство

Tagged with ,

Hunspell

leave a comment »

Hunspell розшифровується як “угорський правопис”, і це найбільш просунута програма перевірки правопису якщо не враховувати Grammarly. А все тому що угорська мова – найбільш скажена в плані морфології. Тому якщо щось підходить для угорської – для інших європейських мов точно підійде.

sudo apt install hunspell hunspell-uk hunspell-de-de libhunspell-dev
sudo pip install hunspell

hunspell має доволі простий інтерфейс для використання в мовах програмування (хоча й складні словники (читати man hunspell.5)):

>>> import hunspell
>>> spellchecker = hunspell.HunSpell('/usr/share/hunspell/uk_UA.dic', '/usr/share/hunspell/uk_UA.aff')
>>> spellchecker.spell('ласка')
True
>>> spellchecker.spell('ласкає')
False
>>> spellchecker.suggest('ласкає')
['ласка', 'ласкам', 'лускає', 'ласках', 'ляскає', 'ласка є', 'скалатає']
Прочитати решту цього запису »

Written by bunyk

13 Січня, 2020 at 19:45

Опубліковано в Інструменти, Кодерство

Tagged with ,

Як збудувати геодезичний купол?

with 4 comments

Я простий програміст, але іноді свербить побути архітектором. Всякий ентерпрайз проектувати ще не пускають, але тут випала нагода зпроектувати проект іграшкової хатинки для донечки. Тому що після покупки меблів в порожню квартиру в нас все ще залишається вільна кімната і в ній куууупа картону.

Картон в меблевій майстерні

Я погуглив які можна робити хатинки і знайшов таке на instructables. Але там виміри подані все ще в імперській системі і я вирішив перерахувати. І взагалі зробити проект правильною водоспадною методологією – поетапно.
Прочитати решту цього запису »

Written by bunyk

21 Грудня, 2019 at 21:22

Корисні налаштування Git

leave a comment »

Перше. Як не задовбувати всіх сміттям яке створює ваше IDE, і за замовчуванням мати gitignore для всіх репозиторіїв.

git config --global core.excludesfile ~/.gitignore

Ця команда відредагує файл ~/.gitconfig і в ~/.gitignore можна буде перелічити всякі там *.swp, чи що там ваш редактор створює.

Друге. Якщо Go не хоче встановлювати модулі з помилкою “fatal: could not read Username for ‘https://github.com&#8217;: terminal prompts disabled”, бо залежності прописані через https, а ви використовєуте SSH, це можна виправити таким налаштуванням:

git config --global url."git@github.com:".insteadOf "https://github.com/"

На цьому поки що все, дякую за увагу. 🙂

Written by bunyk

12 Листопада, 2019 at 12:48

Опубліковано в Кодерство, Конспекти

Tagged with ,

Скільки слів треба щоб написати вікіпедію? І які зустрічаються частіше?

with 4 comments

Виявляється відтоді як я рахував символи вікіпедії пройшло вже більше року. Рахував я їх за допомогою Go, хоча можна було сильно спростити собі життя і рахувати їх за допомогою Python і pywikipediabot. Сьогодні розкажу як, і як можна побачити з назви – рахуватимемо слова.

Я чомусь боявся що щоб порахувати слова пам’яті не вистачить, тому треба якусь базу даних. Або пробувати все в пам’яті, але аби комп’ютеру не стало погано якось обмежити доступну пам’ять. Але мої 4Gb використовувались лише щось трохи більше ніж на 40% для підрахунку всіх слів включно зі сторінками обговорень, категорій, шаблонів, сторінок опису файлів, і т.п. німецької вікіпедії.

В модулі pywikibot.pagegenerators є об’єкти XMLDumpOldPageGenerator і XMLDumpPageGenerator. Вони приймають назву архіву з XML дампом в конструкторі, а після створення по них можна ітеруватися отримуючи об’єкти сторінки. Не ведіться на слово “Old” в назві першого об’єкта, це означає не депрекацію, а те що текст сторінки буде братись з дампа, а в другому випадку з дампа буде братись лише заголовки, а за свіжим текстом зроблять запит, що сповільнить обробку разів в 500. Тобто замість кількох годин ви будете чекати рік. 🙂

Я спробував проаналізувати німецьку вікіпедію (код буде пізніше), на це пішло 694 хв (трохи менше ніж 12 годин) і вийшло що в ній на 6,425,028 сторінках використовується 2,381,457,397 слів (приблизно 371 на сторінку), з них різних слів 18,349,393. В кінцевому результаті CSV з частотним словничком виходить на 300MB.

Серед тих що зустрічаються лише раз є слова типу PikettdienstPikettdienst (помилка парсера який видаляв розмітку), слово – це юридичний термін швейцарської німецької і перекладається як “служба за викликом”. І є слова на зразок Werkshöfe – подвір’я фабрик.

Топ 50 слів виглядає так, і складає 28% всіх слів загалом:

der 58761447 sich 9169933
und 49084873 wurde 9114619
die 44536463 CET 8614461
in 35684744 an 8385637
von 24448221 er 7835324
ist 20614114 dass 7550955
den 19454023 du 7435099
nicht 17519638 bei 7420172
das 17302844 Diskussion 7237855
zu 16167971 aus 7065523
mit 15906145 Artikel 6967243
im 15167140 oder 6824420
des 14661593 werden 6508092
für 14016308 war 6449858
auf 13957013 nach 6426826
auch 12849476 wird 6117566
eine 11903977 aber 6052645
ein 11780352 am 6017703
Kategorie 11369651 sind 5953632
als 11167157 Der 5623930
dem 11124726 Das 5545595
CEST 11104741 einen 5465687
ich 10886406 noch 5409154
Die 10761776 wie 5293658
es 10204681 einer 5228368

До списку потрапили такі вікіпедійно специфічні слова як Kategorie (сторінки без категорій вважаються не комільфо), CEST і CET (центральноєвропейський літній час і центральноєвропейський час, в підписах в обговореннях).

Ну а що без сторінок обговорень? Проблемав тому, що при створенні об’єкту сторінки XMLDumpOldPageGenerator бере з дампа лише текст і заголовок, простір імен залишається не заповненим і за замовчуванням 0 (основний). Є ще поле isredirect так при спробі доступу до нього знову здійснюється запит. Тому, краще перейти на рівень нижче і використати XmlDump з pywikibot.xmlreader, він використовується так само, просто дає об’єкти не Page, а попростіші, які не вміють робити запити до вікіпедії і не мають методу save. Але нам його й не треба, правда?

Ось код який ігнорує перенаправлення і всі сторінки крім статтей:

"""Count word frequencies in wikipedia dump"""
import csv
from collections import Counter
from itertools import islice
import re
import sys

import mwparserfromhell
from pywikibot.xmlreader import XmlDump

def main():
    """Iterate over pages and count words"""
    if len(sys.argv) < 2:
        print('Please give file name of dump')
        return
    filename = sys.argv[1]

    pages = 0
    words = 0
    words_counts = Counter()
    print('Processing dump')

    for page in XmlDump(filename).parse():
        if (page.ns != '0') or page.isredirect:
            continue
        try:
            text = mwparserfromhell.parse(page.text).strip_code()
        except Exception as e:
            print(page.title, e)
            continue

        text = text.replace('\u0301', '') # remove accents
        # Ukrainian: 

        # page_words = re.findall(
        #     r'[абвгґдеєжзиіїйклмнопрстуфхцчшщьюя'
        #     r'АБВГҐДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ’\'-]+',
        #     text
        # )
        
        # Any language:
        page_words = re.findall(r'\b[^\W\d]+\b', text)

        pages += 1
        words += len(page_words)
        words_counts.update(page_words)
        if pages % 123 == 0:
            print('\rPages: %d. Words: %d. Unique: %d. Processing: %s' % (
                pages, words, len(words_counts), (page.title + ' ' * 70)[:70],
            ), end='')

    print('Done. Writing csv')
    with open('common_words.csv', 'w', newline='') as csvfile:
        csvwriter = csv.writer(csvfile)
        for item in words_counts.most_common():
            csvwriter.writerow(item)

if __name__ == '__main__':
    main()

Він працює майже вдвічі швидше, 381 хвилину, бо обробляє лише 2,295,426 сторінок (обсяг німецької вікіпедії цього року). На цих сторінках є 1,074,446,116 слів (в середньому 468 на сторінку), з них різних – 12,002,417. (Виявляється є аж 6 мільйонів всяких слів які вживаються на всіляких службових сторінках німецької вікіпедії, і яких нема в статтях).

Якщо ж взяти українські статті, то на них треба ще менше часу – 131 хвилину (забув уточнити що в мене SSD), їх є 923238 (скоро мільйон!), слів 238263126 (в середньому 258 на сторінку, треба доповнювати 😉 ). З них різних – 4,571,418. Отак, в мене тепер є частотний словник української на 4.5 мільйони слів. І німецької на 12 мільйонів.

Хоча не спішіть з висновками що українська мова бідніша, бо мої методи потребують вдосконалення. По перше, так як Morgen (ранок) і morgen (завтра) – різні слова, то я не приводив букви в німецькій до одного регістру. (Правда й в українській забув це зробити).

По друге, в німецькому словнику 350590 разів зустрічається слово “www”, бо я вважав словом будь-яку послідовність літер латинки, а в українській відфільтрував кирилицю. Слово youtube зустрічається 8375 разів, а значить є ризик знайти якесь рідкісне слово на зразок “fCn8zs912OE”. 🙂

На WordPress глючить додавання картинок, тому нате вам відео:

А, і ось топ-10 української вікіпедії:

в,4551982
на,3730686
і,3475086
у,3353796
з,3053407
-,2695783
Категорія,2417267
та,2350573
до,1815429
року,1553492

Частота “року” наводить на думку що в українській вікіпедії якийсь перекос на історичні методи викладу. 🙂

Written by bunyk

30 Липня, 2019 at 23:24

Kubernetes з microk8s

leave a comment »

Kubernetes – це такий docker-compose на стероїдах, що дозволяє керувати кластером машин на яких запускаються контейнери. Infrastructure as a code, і всяке таке. Дивно що в цьому кібернетичному блозі про кібернетіс ще жодного разу не згадувалось, тому варто цю ситуацію виправити.

Інсталяція

Є різні способи поставити локально однонодовий кластер, minikube (з яким в мене не дуже вийшло), і microk8s, який на Ubuntu, і лінукси в яких є менеджер пакетів Snappy, ставиться так:

sudo snap install microk8s --classic

Це встановить кластер і CLI для керування кластером kubectl. Правда вона називатиметься microk8s.kubectl. Якщо ви не ставили kubectl окремо (можна через той же snap install) для керування кластером десь в хмарах, то можна зробити аліас, а якщо ставили – так можна переконфігурити її для роботи з локальним кластером:

microk8s.kubectl config view --raw > ~/.kube/config

Тоді можна наприклад отримати список нодів кластера:

$ kubectl get nodes
NAME                   STATUS    ROLES     AGE       VERSION
bunyk-latitude-e5470   Ready     <none>    3h        v1.14.0

Логічно що у випадку локальної інсталяції це буде лише один комп’ютер.

Щоб перемкнути kubectl на керування наприклад якимось кластером в хмарах Google, за умови що у вас встановлений gcloud, треба виконати:

gcloud container clusters get-credentials [CLUSTER_NAME]

Аддони й панель керування

Ще microk8s має команди для вмикання (enable) і вимикання (disable) аддонів:

microk8s.enable dns dashboard

dns потрібний для багатьох речей, тому його радять вмикати. dashboard – web UI, і InfluxDB з Grafana для моніторингу ресурсів. Щоб його побачити, треба викликати kubectl proxy і перейти за адресою: http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/#!/login

Сторінка логіну

Там попросять залогінитись, щоб отримати JWT токен для логіну, треба виконати

kubectl -n kube-system get secret
# тоді в списку знайти ім'я що починається з kubernetes-dashboard-token-
# а тоді:
kubectl -n kube-system describe secret kubernetes-dashboard-token-c4bmp

Параметр -n означає простір імен, це щось на зразок директорії де лежать всі об’єкти кластера, наприклад секрети. Це також відображається в шляхах до API, як от /api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/ для доступу до сервісу https:kubernetes-dashboard. За замовчуванням kubectl працює з простором імен default, але у випадку вище, нам треба kube-system.

Запуск контейнерів в Kubernetes

Тепер може спробуємо щось запустити? Для цього треба створити под (pod – англійське слово що позначає групу китів. Вони взагалі дивні слова мають для цього. Зграя сов – це parliament, круків – murder). Под – це група контейнерів зі спільною IP адресою, які запускаються а ноді.

Найпростіший спосіб створити под – майже такий самий як запустити контейнер:

kubectl run nginx --image=nginx
# kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
# deployment.apps/nginx created

Це говорить нам що команда створила deployment, але в майбутньому створюватиме лише поди, якщо не передати параметр --generator=run-pod/v1. Чому так пояснюють тут.

Що таке деплоймент? Нуууу, це важко пояснити, і це мене найбільше в Кубернетісі вибішує. Под – це набір конейнерів зі спільною IP адресою, набором портів, диском, і т.д. Под сам по собі запускати в kubernetes не рекомендують, бо після того як в нього трапиться якась аварія наприклад через закінчення пам’яті, його ніхто не перезапустить. Подом керує контролер, одним з яких є контролер що називається ReplicaSet, який задає кількість копій пода що мають бути запущені. І якщо одна з них з якихось причин здихає – запускається нова, щоб кількість завжди відповідала потрібній. Deployment – об’єкт що містить контролер ReplicaSet, і керує версіями імеджів контейнерів в подах цього контролера. Абстракцій як в TCP/IP…

Тим не менш, ми побачимо под в списку:

$ kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
nginx-7db9fccd9b-w6468   1/1     Running   1          44h

Щоб видалити деплоймент разом з подами дають команду:

kubectl delete deployments/nginx

Трохи складніший спосіб створити под – написати маніфест:

apiVersion: v1 
kind: Pod
metadata:
  name: nginx
spec:
  containers:
    - image: nginx
      name: nginx
      ports:
        - containerPort: 80
          name: http
          protocol: TCP

Якщо його записати в файл, наприклад nginx.yaml, то щоб запустити:

kubectl apply -f nginx.yaml 

Як подивитись що всередині пода? Можна прокинути порт, і тоді те що контейнери в поді віддають на якомусь порті буде доступно на порті localhost:

kubectl port-forward nginx 8088:80

Загальне правило для портів в Kubernetes (бо такі пари порт:порт зустрічаються часто) – зліва порти ззовні, справа – всередині. Якщо все працює, на http://localhost:8088 ви маєте побачити сторінку де пише “If you see this page, the nginx web server is successfully installed and working.”

Можна подивитись логи:

$ kubectl logs -f nginx
127.0.0.1 - - [31/Mar/2019:17:00:53 +0000] "GET /favicon.ico HTTP/1.1" 404 154 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:66.0) Gecko/20100101 Firefox/66.0" "-"
127.0.0.1 - - [31/Mar/2019:17:01:56 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:66.0) Gecko/20100101 Firefox/66.0" "-"

Як змінити те що под показує на головній? Створити якийсь html файл і закинути його командою:

kubectl cp index.html nginx:/usr/share/nginx/html/index.html

Хоча так не прийнято робити, і можна хіба що під час розробки. Краще додати файли в імедж за допомогою Dockerfile.

Запуск сайту

Але давайте вже зробимо щось серйозне на кілька контейнерів. Наприклад як в цій публікації було за допомогою docker compose, тільки за допомогою kubernetes: два контейнери, один з них nginx веб-сервер що віддає статичні файли для фронт-енду, інший – API на python що віддає дані графіків.

Таким чином файли backend.docker, dashboard.html і server.py можна скопіювати собі в проект без змін (звідси). nginx.docker напевне краще називати frontend.docker, і помістити туди лише файли фронт-енду:

FROM nginx

COPY dashboard.html /usr/share/nginx/html/index.html

Конфігурацію nginx ми змінювати не будемо, бо відповідальним за диспетчеризацію запитів між фронт-ендом і бекендом в нас буде штука що називається Ingress.

Тут, на відміну від docker-compose який сам наші контейнери може зібрати, їх треба створити вручну:

docker build -t frontend -f frontend.docker .
docker build -t backend -f backend.docker .

Покладемо конфіг для двох деплойментів у файл site.yaml і скажемо кластеру оновитись (kubectl apply -f site.yaml):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-deployment
spec:
  selector:
    matchLabels:
      tier: frontend
  replicas: 1
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: frontend
        image: frontend
        ports:
        - containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend-deployment
spec:
  selector:
    matchLabels:
      tier: backend
  replicas: 2 # більше подів для бекенду, бо йому самому може важко.
  template:
    metadata:
      labels:
        tier: backend
    spec:
      containers:
      - name: backend
        image: backend
        ports:
        - containerPort: 80

Один файл в Kubernetes може містити описи багатьох об’єктів, розділені рядком що містить “—“. Так простіше працювати, бо треба менше команд kubectl apply, чи kubectl delete.

Якщо kubectl get pods показує що наші поди мають статус ErrImagePull або ImagePullBackOff, це означає що kubernetes намагається взяти імеджі не з нашого комп’ютера, а з докерхабу.

Виявляється треба ще додати їх в реєстр microk8s. Для цього:

microk8s.enable registry

docker tag backend localhost:32000/backend
docker push localhost:32000/backend
docker tag frontend localhost:32000/frontend
docker push localhost:32000/frontend

Підозрюю що це можна було зробити простіше і швидше якщо зразу називати імеджі правильним тегом. Але майстерність приходить з досвідом. 🙂 В кінцевому результаті ви маєте мати три запущені поди, в двох деплойментах.

І що з того? Поки нічого, бо IP адреси цих подів динамічно міняються (коли їх перезапускають). Для того щоб мати постійний доступ потрібен сервіс, який проксює доступ до подів заданих мітками (labels). Мітки це пари ключ-значення які чіпляються до об’єктів в Kubernetes. Коли ми в описі пода писали:

labels: 
  tier: backend

То це ми йому якраз задавали мітки. Тепер по мітках ми можемо ці об’єкти отримувати:

bunyk@bunyk-thinkpad:~/projects/dockerizing$ kubectl get pods -l tier=frontend
NAME                                   READY   STATUS    RESTARTS   AGE
frontend-deployment-695cfcc94c-jl5hg   1/1     Running   0          3h6m
bunyk@bunyk-thinkpad:~/projects/dockerizing$ kubectl get pods -l tier=backend
NAME                                  READY   STATUS    RESTARTS   AGE
backend-deployment-669d885465-cfbrc   1/1     Running   0          3h6m
backend-deployment-669d885465-nh8lg   1/1     Running   0          3h6m

Так само сервіс має надає доступ з постійним IP до набору подів заданого мітками. Сервіси створюються так:

apiVersion: v1
kind: Service
metadata:
  name: backend
spec:
  selector:
    tier: backend
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: frontend
spec:
  selector:
    tier: frontend
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

Сервіс має селектор що визначає за якими подами стежити, і відкриває порти. port – це який порт відкрити, targetPort – це до якого порта в поді приєднатись. За цим треба слідкувати, бо якщо не виконається одна з умов: порт на якому слухає сервер в контейнері == containerPort, containerPort == targetPort сервіса, port сервіса == порт до якого приєднується клієнт, то отримаємо помилку “Connection refused” чи подібну.

Після чергового kubectl apply -f site.yaml можна подивитись які сервіси отримуємо:

$ kubectl get services
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
backend      ClusterIP   10.152.183.69   <none>        80/TCP    110m
frontend     ClusterIP   10.152.183.63   <none>        80/TCP    30m
kubernetes   ClusterIP   10.152.183.1    <none>        443/TCP   8d
$ curl 10.152.183.69/data/1
[1.0997977838,0.6222197737,0.7265324166,1.0475918458,0.8271129655,0.6489646475,0.3625859258,0.7692987393,1.1331619921,1.4889188394]

Бачимо що сервіси які ми створюємо мають тип ClusterIP. Це тип за замовчуванням, і означає що він буде доступний лише з середини кластера. Нам доступний, бо ми ж сидимо на одній єдиній ноді кластера. Крім нього є ще NodePort, LoadBalancer і ExternalName, але розбиратись що це – ми не будемо, бо й без того голова вже пухне (чи у вас ні?).

Залишився ще Ingress. Це штука що дає доступ до сервісів кластера ззовні кластера. Конфігурується так:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: entrypoint
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - http:
      paths:
      - path: /api/(.*)
        backend:
          serviceName: backend
          servicePort: 80
      - path: /(.*)
        backend:
          serviceName: frontend
          servicePort: 80

Перед тим як її створювати, треба ще виконати microk8s.enable ingress.

Тут важливий параметр nginx.ingress.kubernetes.io/rewrite-target, який означає “передавати сервісу запит замінивши URL на той що вказано, підставивши групи з регулярного виразу в path“.

Після застосування цієї конфігурації, на localhost в нас завантажиться фронтенд, пошле через ingress запити до бекенду, і все навіть буде через HTTP 2.0.

Питайтесь якщо що не виходить чи не доходить, в мене теж багато з того що тут написано не виходило зразу, може я вже стикався з тими проблемами що у вас.

Written by bunyk

7 Квітня, 2019 at 00:21

Опубліковано в Кодерство, Конспекти

Tagged with

Хакери викрали базу даних пін-кодів!

with 10 comments

Важлива інформація для власників банківських карток Visa і MasterCard, поділіться з друзями і знайомими щоб вони теж були попереджені.

Нижче викладаю копію бази даних з вкраденими пін-кодами. Перевірте чи ваш є серед них, і якщо є – рекомендую змінити на якийсь інший, якого нема серед списку зламаних.
Прочитати решту цього запису »

Written by bunyk

1 Квітня, 2019 at 00:59

Опубліковано в Кодерство

Tagged with