Tag Archives: linux

Kubernetes з microk8s

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

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

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

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

Docker і обмеження ресурсів

Раніше я вже писав собі шпаргалку по докеру, яка нікому крім мене майже не потрібна, тут буде додаток до неї.

Контейнери докера – це аналог процесів в ОС – тобто щось що запущено виконується. Запускаються імеджі (аналог виконуваної програми). Можна взяти готовий імедж, можна зробити свій за допомогою докерфайла (аналог коду програми), який описує як білдиться (аналог компіляції) імедж.

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

docker run $image_name [$command]

Наприклад якщо цікаво виконати якийсь код на останньому Python, але лінь його ставити, докер скачає і виконає:

docker run python:latest python -c "import sys; print(sys.version)"
# Unable to find image 'python:latest' locally
# latest: Pulling from library/python
# 22dbe790f715: Pull complete 
# ...
# 61b3f1392c29: Pull complete 
# Digest: sha256:c32b1a9fb8a0dc212c33ef1d492231c513fa326b4dae6dae7534491c857af88a
# Status: Downloaded newer image for python:latest
# 3.7.2 (default, Mar  5 2019, 06:22:51) 
# [GCC 6.3.0 20170516]

Якщо не передавати ніяку команду, контейнер виконуватиме ту що для нього задана за замовчуванням. Наприклад

docker run --name test_python_run python:latest # задаємо контейнеру ім'я щоб не сплутати з іншими:
docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS                    NAMES
d95e1e13e3f2        python:latest       "python3"                5 seconds ago       Exited (0) 4 seconds ago                             test_python_run

Бачимо що контейнер запускав команду “python3” але вийшов з неї (бо термінал не приєднався). Щоб увійти в інтерактивну сесію, треба запускати так (-i вроді означає інтерактивно, тобто очікувати на stdin, -t – приєднати до поточного терміналу):

docker run -it python:latest

Тільки через командний рядок багато Python коду не передаш. Тому є два варіанти передати файли в контейнер. Перший – прямо в image, за допомогою dockerfile.

Візьмемо для експерименту такий скрипт що поступово пробує використати все більше й більше пам’яті:

import random
import time

data = []
for i in range(10 ** 6):
    data.append(random.random())
    if i % 1000 == 0:
        print(len(data))
        time.sleep(0.25)

Managing memory in Python is easy—if you just don’t care. Документація Theano.

Щоб створити з ним імедж достатньо такого докерфайлу:

FROM python:3.7
COPY script.py ./script.py
CMD python script.py

Тепер, щоб створити з нього імедж який називається наприклад memeater (зжирач пам’яті), треба виконати:

docker build -t memeater -f Dockerfile .

А щоб потім запустити цей контейнер:

docker run -t memeater

-t щоб бачити що він пише в stdout.

Далі ми можемо за допомогою команди docker stat спостерігати за тим скільки ресурсів цей контейнер їсть:

CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT   MEM %               NET I/O             BLOCK I/O           PIDS
8a58c19cc93c        exp                 0.28%               6.02MiB / 10MiB     60.20%              3.17kB / 0B         565kB / 1.01MB      2

Аби він не з’їв всю доступну пам’ять, можна йому обмежити ресурси:

docker run -t --name experiment --memory="10M" --cpus=0.1 memeater

Якщо вискакує повідомлення “WARNING: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.”, значить у вас трохи не такий Linux, і обмеження стосуватиметься лише RAM, а не області підкачки. Задати параметр --memory-swap теж не допоможе.

Допоможе – взагалі відключити зберігання сторінок на диск.

docker run -t --name experiment --memory="20M" --memory-swappiness=0 --cpus=0.1 memeater

Якщо отримуєте помилку “docker: Error response from daemon: OCI runtime create failed: container_linux.go:344: starting container process caused “process_linux.go:424: container init caused \”\””: unknown.”, то це тому що обмеження по пам’яті за сильне. В мене при 10M вискакує, при 20 – ні.

Що відбувається коли пам’ять закінчується? Лог закінчується так:

492001
Killed

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

Моніторинг випадкової змінної за допомогою Telegraf -> InfluxDB -> Grafana

В цій публікації я розкажу про те як побудувати графік зміни якоїсь змінної в реальному часі. Наприклад якоїсь ціни, чи кількості запитів до сервера. Ключові слова: Docker, Docker compose, time series database, InfluxDB, Grafana, Telegraf. Всі крім докера будуть пояснені детально, докер – лише використовуватись для економії часу на інсталяцію.

В тренді зараз криптовалюти, тому давайте для прикладу будемо моніторити курс Litecoin до гривні. Для цього достатньо зробити GET запит https://api.coinmarketcap.com/v2/ticker/2/?convert=UAH. Для Bitcoin замініть id після /ticker/ з 2 на 1. (Документація з API). Він повертає JSON, формат якого розберемо трохи пізнішео. Бо нам ще треба встановити, налаштувати і запустити три програми для того щоб вони одна з одною працювали. Ну звісно в наш час це вручну ніхто не робить, тому ось вам готова конфігурація docker-compose.yml:

version: '3'
services: 
    influxdb:
      image: influxdb:latest
      container_name: influxdb
      ports:
        - "8086:8086"
      networks:
        - back-tier

    telegraf:
      image: telegraf:latest
      container_name: telegraf
      volumes:
        - ./telegraf.conf:/etc/telegraf/telegraf.conf:ro
      networks:
        - back-tier


    grafana:
      image: grafana/grafana:latest
      container_name: grafana
      ports:
        - "3000:3000"
      networks:
        - back-tier


networks:
  back-tier:

Записуєте його в директорію проекту, командуєте docker-compose up, і насолоджуєтесь логами всіх трьох сервісів. Правда вискочить помилка, бо конфігурація

volumes:
        - ./telegraf.conf:/etc/telegraf/telegraf.conf:ro

означає “покласти файл telegraf.conf з поточної директорії, в контейнер за шляхом /etc/telegraf/telegraf.conf”, а ми цей файл не написали. Для того треба спершу розібратись що таке Telegraf, чим він займатиметься, і як.

Telegraf, як пишуть на його сайті – це агент для збирання метрик і запису їх в InfluxDB, чи якісь інші можливі місця. Його файл конфігурації довгий, але важливі лише два місця:

[[outputs.influxdb]]
  # Конфігурація виведення даних в InfluxDB
  urls = ["http://influxdb:8086"] # HTTP інтерфейс InfluxDB. 
  ## Ім'я домену influxdb буде показувати на контейнер influxdb, тому що docker-compose так робить мережі

  ## База даних в яку писати метрики (telegraf її створить якщо буде потреба).
  database = "telegraf"

... 

[[inputs.http]]
  ## Брати дані http запитами
  urls = [ # звідки
    "https://api.coinmarketcap.com/v2/ticker/2/?convert=UAH"
  ]
  method = "GET" # методом GET
  data_format = "json" # розшифровувати як JSON

Якщо такий файл в нас є, то композ запустить всі три сервіси успішно, і Telegraf почне писати щось в InfluxDB. Пора подивитись що з того вийде. Щоб зайти в інтерфейс командного рядка Influxdb треба виконати команду

docker exec -it influxdb influx

А тоді:

> SHOW DATABASES
name: databases
name
----
_internal
telegraf
> use telegraf
Using database telegraf
> SHOW MEASUREMENTS
name: measurements
name
----
http

Бачим що Telegraf пише все в одну “таблицю” (measurement) – http. Але це насправді не страшно, бо в InfluxDB важливі не так measurements, як series – measurement з унікальним набором тегів (полів що індексуються). Крім них ще є fields (поля, які містять дані і не індексуються). Подивимось які в нас теги і поля (це майже те саме що схема таблиці в реляційних БД):

> SHOW FIELD KEYS FROM http 
name: http
fieldKey                           fieldType
--------                           ---------
data_circulating_supply            float
data_id                            float
data_last_updated                  float
data_max_supply                    float
data_quotes_UAH_market_cap         float
data_quotes_UAH_percent_change_1h  float
data_quotes_UAH_percent_change_24h float
data_quotes_UAH_percent_change_7d  float
data_quotes_UAH_price              float
data_quotes_UAH_volume_24h         float
data_quotes_USD_market_cap         float
data_quotes_USD_percent_change_1h  float
data_quotes_USD_percent_change_24h float
data_quotes_USD_percent_change_7d  float
data_quotes_USD_price              float
data_quotes_USD_volume_24h         float
data_rank                          float
data_total_supply                  float
metadata_timestamp                 float
> SHOW TAG KEYS FROM http
name: http
tagKey
------
host
url
> SHOW TAG VALUES FROM http WITH KEY IN ("host", "url")
name: http
key  value
---  -----
host 42bdec9c8910
url  https://api.coinmarketcap.com/v2/ticker/2/?convert=UAH

Бачимо що теги – це хост на якому запущений агент телеграфа що прислав дані (дивне в нього id, але це id контейнера). Не знаю чому не ім’я, думаю якось можна змінити, але це не дуже важливо якщо в нас один сервер з Telegraf. І адреса ресурсу який моніторить Telegraf. Тому можна бути спокійним з “таблиці” http можна буде вибрати окремі значення за тегом.

А от fields – дійсно багато. Яке з них – ціна Litecoin? Ну, для цього треба подивитись який JSON нам віддав coinmarketcap:

{
    "data": {
        "id": 2, 
        "name": "Litecoin", 
        "symbol": "LTC", 
        "website_slug": "litecoin", 
        "rank": 6, 
        "circulating_supply": 57387708.0, 
        "total_supply": 57387708.0, 
        "max_supply": 84000000.0, 
        "quotes": {
            "UAH": {
                "price": 2041.6651371095, 
                "volume_24h": 6550472764.2681465, 
                "market_cap": 117166483500.0, 
                "percent_change_1h": 0.08, 
                "percent_change_24h": 1.43, 
                "percent_change_7d": -6.39
            }, 
            "USD": {
                "price": 77.8638, 
                "volume_24h": 249818000.0, 
                "market_cap": 4468425048.0, 
                "percent_change_1h": 0.08, 
                "percent_change_24h": 1.43, 
                "percent_change_7d": -6.39
            }
        }, 
        "last_updated": 1531505650
    }, 
    "metadata": {
        "timestamp": 1531505337, 
        "error": null
    }
}

Ціна лежить в data.quotes.UAH.price, тому думаю нас цікавить поле data_quotes_UAH_price. Спробуємо запит:

> SELECT data_quotes_UAH_price FROM http WHERE time >= now() - 1h
name: http
time                data_quotes_UAH_price
----                ---------------------
1531595740000000000 2007.3576069685
1531595750000000000 2007.3576069685
1531595760000000000 2007.3576069685
1531595770000000000 2006.7466581361
1531595780000000000 2006.7466581361
1531595790000000000 2006.7466581361
1531595800000000000 2006.7466581361
1531595810000000000 2006.7466581361
...

О, це щось з чого можна будувати графік! І цим займеться Grafana.

Вона в нашій системі працює на порті 3000, тому заходимо на http://localhost:3000/ , входимо як USER: admin, PASSWORD: admin, змінюємо пароль, натискаємо “Create datasource”, заповнюємо форму для InfluxDB:

Заповнення джерела даних в Grafana

Внизу треба ще не забути вибрати базу даних “telegraf”, і натиснути “Save & test”. Якщо вискочило зелене повідомлення (а не червоне про помилку), то можна продовжувати.

Натискаємо плюсик -> Create -> Dashboard, додаємо панель “Graph”. У вкладці “Metrics” вибираємо датасорс InfluxDB і пишемо запит. Там є конструктор запитів, виглядає все так:

Побудова графіка за запитом

Але при бажанні можна справа натиснути кнопку меню, вибрати “Toggle edit mode”, і відредагувати запит як SQL:

SELECT mean("data_quotes_UAH_price") FROM "http" WHERE $timeFilter GROUP BY time($__interval) fill(null)

Бачимо що Grafanа вставляє в запит свої змінні, що дозволяє інтерактивно перебудовувати графік. Змінна $timeFilter містить щось на зразок now() - 1h залежно від того що користувач вибере в полі вгорі дашборда:

Вибір інтервалу часу

Ну як, почуваєтесь трошки фінансистами? Я ні, я аналіз даних в універі проспав :(, і взагалі мені це все для того аби рахувати запити до сервера.

А, ну і ввесь код, можна взяти на https://github.com/bunyk/docker-influxdb-grafana

Як помиготіти клавіатурою ноутбука Dell в Linux

Для тих кому мало гірлянди на свята 😉 . Якщо в файл /sys/devices/platform/dell-laptop/leds/dell::kbd_backlight/brightness записати ціле число від 0 до 2, то це встановлює відповідну яскравість підсвітки клавіатури. Наприклад:

import time

with open('/sys/devices/platform/dell-laptop/leds/dell::kbd_backlight/brightness', 'w') as brightness:
    i = 0
    while True:
        i += 1
        time.sleep(0.2)
        brightness.write(str(i % 3))
        brightness.flush()

Filed under: Кодерство Tagged: linux

Встановлення Python 3.5 з джерельного коду, встановлення Django

Ок, продовжу спроби підготуватись до DjangoGirls так, щоб там ми вчили найпередовіші технології. :)

Такі експерименти краще робити у захищеному середовищі, тому бажано щоб у вас були VirtualBox та Vagrant:

sudo apt-get install virtualbox vagrant

Поки вони ставляться, раджу коротко ознайомитись як користуватись тим Vagrant-ом.

В директорії з кодом створюємо такий файл:

# -*- mode: ruby -*-
# vi: set ft=ruby :

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "ubuntu/trusty64"
  config.vm.network "forwarded_port", guest: 8000, host: 8000
end

Це поки що він такий. Пізніше поміняю, і вся інсталяція має більш автоматизуватись. Тепер команда vagrant up дозволяє підняти чисте середовище. vagrant ssh – зайти в його термінал.

Ми хочемо Python 3.5, а його інакше як з сорсів не отримаєш, тому качаємо з сайту: https://www.python.org/downloads/

sudo apt-get update
sudo apt-get install build-essential libsqlite3-dev sqlite3 bzip2 libbz2-dev
sudo apt-get install libreadline-dev libncurses5-dev tk-dev libssl-dev
wget -c https://www.python.org/ftp/python/3.5.0/Python-3.5.0.tar.xz
tar xJf Python-3.5.0.tar.xz
cd Python-3.5.0
./configure
make
sudo make install

Тепер нарешті можна створювати віртуальне середовище (всередині віртуальної машини, ага), і ставити Django:

mkdir djangogirls
cd djangogirls
python3.5 -m venv myenv
source myenv/bin/activate
pip install django

Та-дааммм!!!

Successfully installed django-1.9

Не знав що вже є Django 1.9. Хоча, звісно що не знав, його вчора випустили. Кажуть там змінили дизайн адмінки. Давайте швиденько подивимось:

django-admin startproject mysite .
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver
Адмінка як адмінка.

Адмінка як адмінка.


Filed under: Інструменти, Кодерство Tagged: linux, Python

Як намалювати стрілочку в SVG

Креслення стрілочки, з позначенням деяких змінних

Креслення стрілочки, з позначенням деяких змінних

Поточна ситуація така, що на запит “як намалювати стрілочку”, Google видає купу порад дівчатам про те як зашпаклювати лице. Але проблема трапляється часто, і не тільки в SVG, ось наприклад старий пост про те як малювати вектори в OpenGL, для програмки що проводить структурний аналіз кінематики машин і механізмів. Тому треба виправити цю ситуацію, і написати ще пару публікацій про малювання стрілочок. :)

Тут буде код який було весело писати, і яким варто поділитись. Присутній також JsFiddle. Код дозволяє малювати стрілочки наступного вигляду:

arrows


Написано з використанням D3.js, але код можна причепити де завгодно, так як головне тут – функція arrow_path, яка генерує значення атрибуту d для тега path. Приймає вона координати початку і кінця стрілки, ширину лінії стрілки, радіус (задає розмір трикутника на кінці стрілки, і радіус gizmo (пімпочки на середині)). directed – булевий аргумент, що вказує чи малювати стрілочку на кінці лінії взагалі. gizmo – якщо false – пімпочки не буде, 'circle' – буде коло, 'diamond' – буде ромбик.

Думаю тут можна було б ще зекономити на ручному перетворенні систем координат, бо виходить забагато арифметики. Натомість використати translate, але щось зразу не додумався. Правда воно і так не тормозить, навіть коли малює отаке чудо:

large_map

Сучасні браузери – потужні машини!

var panel = d3.select('body');

svg = panel.append('svg')
    .attr('width', 500)
    .attr('height', 500);


var arrow_path = function(x1, y1, x2, y2, width, r, directed, gizmo) {
    var dx = x2 - x1; // direction of arrow
    var dy = y2 - y1;
    var l = Math.sqrt(dx * dx + dy * dy); // length of arrow
    var fx = dx / l; // forward vector
    var fy = dy / l;
    
    var lx = -fy; // side vector
    var ly = fx;
    
    var line_rectangle = [
        (x1 + lx*width) + ',' + (y1 + ly*width),
        (x2 + lx*width) + ',' + (y2 + ly*width),
        (x2 - lx*width) + ',' + (y2 - ly*width),
        (x1 - lx*width) + ',' + (y1 - ly*width)
    ];
    
    var alx, aly, arx, ary;
    if (directed) {
        alx = x2 - fx*r*2 + lx*r;
        aly = y2 - fy*r*2 + ly*r;
        arx = x2 - fx*r*2 - lx*r;
        ary = y2 - fy*r*2 - ly*r;
    };
    
    var get_end_points = function () {
        // return list of end vertexes that for an arrow or just side of rectangle.
        if(directed) {            
            return [
                'L' + (x2 - fx*r*2 + lx * width) + ',' + (y2 - fy*r*2 + ly * width),
                'L' + (x2 - fx*r*2 + lx * r) + ',' + (y2 - fy*r*2 + ly * r),
                'L' + x2 + ',' + y2,
                'L' + (x2 - fx*r*2 - lx * r) + ',' + (y2 - fy*r*2 - ly * r),
				'L' + (x2 - fx*r*2 - lx * width) + ',' + (y2 - fy*r*2 - ly * width),
            ];      
        } else {
            return [
                'L' + line_rectangle[1],
                'L' + line_rectangle[2],
            ];

        };
    };
    
    if (!gizmo) {
        return [
            'M' + x1 + ',' + y1,
            'L' + line_rectangle[0]
        ].concat(
            get_end_points(),
            [
                'L' + line_rectangle[3],
                'L' + x1 + ',' + y1,
            ]
        ).join(' ');
    };
    
    var cx = (x1 + x2) / 2;
    var cy = (y1 + y2) / 2;
    var h = Math.sqrt(r*r - width*width);
    
    var arc_rectangle = [
        (cx - fx*h + lx*width) + ',' + (cy - fy*h + ly*width),
        (cx + fx*h + lx*width) + ',' + (cy + fy*h + ly*width),
        (cx + fx*h - lx*width) + ',' + (cy + fy*h - ly*width),
        (cx - fx*h - lx*width) + ',' + (cy - fy*h - ly*width),
    ];
    
    if (gizmo === 'circle') {
        return [
            'M' + x1 + ',' + y1,
            'L' + line_rectangle[0],
            'L' + arc_rectangle[0],
            'A' + r + ',' + r + ' 0 0,0 ' + arc_rectangle[1],
        ].concat(
            get_end_points(),
            [
                'L' + arc_rectangle[2],
                'A' + r + ',' + r + ' 0 0,0 ' + arc_rectangle[3],
                'L' + line_rectangle[3],
                'L' + x1 + ',' + y1,
            ]
        ).join(' ');
    };
    
    if (gizmo === 'diamond') {
        return [
            'M' + x1 + ',' + y1,
            'L' + line_rectangle[0],
            'L' + arc_rectangle[0],
            'L' + (cx + lx * r) + ',' + (cy + ly*r),
            'L' + arc_rectangle[1],
        ].concat(
            get_end_points(),
            [
                'L' + arc_rectangle[2],
                'L' + (cx - lx * r) + ',' + (cy - ly*r),
                'L' + arc_rectangle[3],
                'L' + line_rectangle[3],
                'L' + x1 + ',' + y1,
            ]
        ).join(' ');
    };
    
    throw 'Unknown gizmo value'
};

svg.append('path')
    .attr("d", arrow_path(0, 100, 200, 300, 2, 10, false, 'circle'));

svg.append('path')
    .attr("d", arrow_path(50, 100, 250, 300, 2, 10, false, false));

svg.append('path')
    .attr("d", arrow_path(100, 100, 300, 300, 2, 10, true, false));

svg.append('path')
    .attr("d", arrow_path(150, 100, 350, 300, 2, 10, true, 'circle'));

svg.append('path')
    .attr("d", arrow_path(200, 100, 400, 300, 2, 10, true, 'diamond'));

В кінцевому результаті виходить подібний SVG:

<path d="M150,100 L148.5857864376269,101.41421356237309 L241.6575832073514,194.4860103320976 A10,10 0 0,0 255.51398966790242,208.3424167926486 L334.44365081389594,287.27207793864216 L328.7867965644036,292.9289321881345 L350,300 L342.9289321881345,278.7867965644036 L337.27207793864216,284.44365081389594 L258.34241679264863,205.5139896679024 A10,10 0 0,0 244.4860103320976,191.6575832073514 L151.4142135623731,98.58578643762691 L150,100">

<path d="M200,100 L199.29289321881345,100.70710678118655 L292.2572695790783,193.6714831414514 L292.9289321881345,207.07106781186548 L306.3285168585486,207.7427304209217 L385.1507575950825,286.5649711574556 L378.7867965644036,292.9289321881345 L400,300 L392.9289321881345,278.7867965644036 L386.5649711574556,285.1507575950825 L307.7427304209217,206.3285168585486 L307.0710678118655,192.92893218813452 L293.6714831414514,192.2572695790783 L200.70710678118655,99.29289321881345 L200,100">

Страшненько, в порівнянні з line тому добре що його можна не руками писати.

P.S. Є ще простіший спосіб – називається SVG marker. Правда з ним біда – маркер має окремі обробники для всіх подій миші, тому якщо вішати функції на ці події – якщо миша буде над маркером а не над лінією – не спрацює. Інша проблема – маркер не змінює колір коли змінювати колір лінії. Цей код уникає цих двох проблем. Але якщо вас події і кольори не цікавлять – користуйтесь маркерами.


Filed under: Графіка, Кодерство Tagged: графіка, JavaScript, linux

PostgreSQL & PostGIS “Hello world”

Інсталяція:

sudo apt-get install -y postgresql postgresql-contrib postgis

Після чого від імені користувача postgres (це суперкористувач для postgres), створюємо користувача gisuser та базу для нього, яку наприклад назвемо gis:

sudo -u postgres createuser gisuser
sudo -u postgres createdb --encoding=UTF-8 --owner=gisuser gis
# і створимо пароль для користувача:
sudo -u postgres psql -d gis -c "ALTER USER gisuser WITH PASSWORD 'password';"

# і увімкнути розширення postgis:
sudo -u postgres psql -d gis -c "CREATE EXTENSION postgis;"
sudo -u postgres psql -d gis -c "CREATE EXTENSION postgis_topology;"
CREATE EXTENSION

Тепер ми можемо з’єднуватись з нашою базою за допомогою клієнта, вказавши базу і користувача:

psql -d gis -U gisuser

У випадку отримання помилки:

psql: FATAL:  Peer authentication failed for user

Треба відредагувати файл sudo vim /etc/postgresql/9.4/main/pg_hba.conf, чи який там буде для вашої версії, і зробити таку заміну десь ближче до кінця:

- local   all             all                                     peer
+ local   all             all                                     md5

Тоді можна буде логінитись (дивно, але в мене навіть пароль не питає), і виконувати запити:

select postgis_full_version();
-- В мене дає щось схоже на:
-- POSTGIS="2.1.5 r13152" GEOS="3.4.2-CAPI-1.8.2 r3921"
-- PROJ="Rel. 4.8.0, 6 March 2012" GDAL="GDAL 1.11.2, released 2015/02/10"
-- LIBXML="2.9.2" LIBJSON="UNKNOWN" RASTER

Можна ще поставити гарний GUI-клієнт:

sudo apt-get install -y pgadmin3

Гаразд, давайте тепер створимо табличку з містами:

CREATE TABLE cities ( 
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    location GEOGRAPHY(POINT,4326)
);

4326 це ідентифікатор системи координат (SRID, Spatial Reference system ID) і означає що ми використовуватимемо систему координат WGS 84. Здається це популярна система координат.

Тепер заповнимо таблицю якимись даними:

INSERT INTO cities (name, location) VALUES
 ('Львів', ST_GeographyFromText('SRID=4326;POINT(49.83 24.014167)') ),
 ('Київ', ST_GeographyFromText('SRID=4326;POINT(50.45 30.523611)') ),
 ('Івано-Франківськ', ST_GeographyFromText('SRID=4326;POINT(48.922778 24.710556)') )
;

І спробуємо зробити якийсь запит, наприклад скільки метрів від Львова до Києва:

select ST_Distance(
    (select location from cities where name='Львів'),
    (select location from cities where name='Київ')
);

І цей запит дає мені 723904.293225235, що приблизно 723 км, в той час як до Києва по прямій менш ніж 500 км. Я вибрав неправильну проекцію, або переписав з вікіпедії неправильні координати, або передав функції якісь неправильні параметри. :(

Але на сьогодні напевне просто піду спати. До речі, може ви підкажете в чому помилка?

Посилання


Filed under: Інструменти, Кодерство Tagged: linux

Шпаргалка по Docker

Docker в порівнянні з гіпервізором другого типу.

Docker в порівнянні з гіпервізором другого типу.

Докер – штука для керування лінукс-контейнерами. А Лінукс-контейнери – це особливий вид гіпервізора, який дозволяє створювати на лінуксі віртуальні лінукси. Це мінус що тільки лінукси, але плюс що ядро операційної системи для кожного контейнера спільне, тому ці контейнери набагато легші в порівнянні з повноцінними віртуальними машинами.

Віртуальні машини корисні для ізоляції середовища. Наприклад середовища розробки. Хоча цим може займатись і Vagrant. А ще Vagrant може керувати не тільки машинами на VirtualBox, а й контейнерами Docker. Коли що використувати – здається справа особистих вподобань. Хоча мені кажуть що так як контейнери більш легковісні, тут інша філософія роботи, наприклад “кожному процесу свій контейнер”. Ось цікаве обговорення питання що коли варто використовувати, в якому беруть участь автор Vagrant та автор Docker.

Але краще раз попробувати ніж сто разів прочитати:

Інсталяція

На Linux найпростіше, хоч і не безпечно:

wget -qO- https://get.docker.com/ | sh

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

bunyk@ubuntu:~$ docker run docker/whalesay cowsay boo 
Post http:///var/run/docker.sock/v1.19/containers/create: dial unix 
/var/run/docker.sock: no such file or directory.
Are you trying to connect to a TLS-enabled daemon without TLS?

Якщо бачите таку помилку – значить або докер ще не запущений:

bunyk@ubuntu:~$ sudo service docker start

Або ваш користувач не знаходиться в групі докера:

sudo usermod -aG docker bunyk

Різні інші дії

# скачати образ (щоб він не качався коли ми будемо робити йому run)
docker pull

# список всіх скачаних образів
docker images

# всі запущені контейнери
docker ps

# всі (не лише запущені) контейнери
docker ps -a

# показати лише ідентифікатори контейнерів
docker ps -q

 # видалити всі контейнери
docker rm $(docker ps -aq)

# останній запущений контейнер
docker ps -l

# видалити контейнер
docker rm

# видалити образ
docker rmi

# запустити інтерактивну програму в контейнері
docker run -t -i debian /bin/bash

# запустити демона в контейнері
docker run -d debian /bin/sh -c "while true; do echo hello world; sleep 1; done"

# запустити контейнер так, що директорію хоста /host/dir буде змонтовано як /container/dir
docker run -v /host/dir:/container/dir debian

# слідкувати за логами демона в контейнері
docker logs -f ecstatic_lovelace

# збудувати образ з Dockerfile поточної директорії
docker build -t ouruser/ourrepo .

Filed under: Інструменти, Кодерство Tagged: linux

Шпаргалка по Docker

Docker в порівнянні з гіпервізором другого типу.

Docker в порівнянні з гіпервізором другого типу.

Докер – штука для керування лінукс-контейнерами. А Лінукс-контейнери – це особливий вид гіпервізора, який дозволяє створювати на лінуксі віртуальні лінукси. Це мінус що тільки лінукси, але плюс що ядро операційної системи для кожного контейнера спільне, тому ці контейнери набагато легші в порівнянні з повноцінними віртуальними машинами.

Віртуальні машини корисні для ізоляції середовища. Наприклад середовища розробки. Хоча цим може займатись і Vagrant. А ще Vagrant може керувати не тільки машинами на VirtualBox, а й контейнерами Docker. Коли що використувати – здається справа особистих вподобань. Хоча мені кажуть що так як контейнери більш легковісні, тут інша філософія роботи, наприклад “кожному процесу свій контейнер”. Ось цікаве обговорення питання що коли варто використовувати, в якому беруть участь автор Vagrant та автор Docker.

Але краще раз попробувати ніж сто разів прочитати:

Інсталяція

На Linux найпростіше, хоч і не безпечно:

wget -qO- https://get.docker.com/ | sh

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

bunyk@ubuntu:~$ docker run docker/whalesay cowsay boo 
Post http:///var/run/docker.sock/v1.19/containers/create: dial unix 
/var/run/docker.sock: no such file or directory.
Are you trying to connect to a TLS-enabled daemon without TLS?

Якщо бачите таку помилку – значить або докер ще не запущений:

bunyk@ubuntu:~$ sudo service docker start

Або ваш користувач не знаходиться в групі докера:

sudo usermod -aG docker bunyk

Різні інші дії

# скачати образ (щоб він не качався коли ми будемо робити йому run)
docker pull

# список всіх скачаних образів
docker images

# всі запущені контейнери
docker ps

# всі (не лише запущені) контейнери
docker ps -a

# показати лише ідентифікатори контейнерів
docker ps -q

 # видалити всі контейнери
docker rm $(docker ps -aq)

# останній запущений контейнер
docker ps -l

# видалити контейнер
docker rm

# видалити образ
docker rmi

# запустити інтерактивну програму в контейнері
docker run -t -i debian /bin/bash

# запустити демона в контейнері
docker run -d debian /bin/sh -c "while true; do echo hello world; sleep 1; done"

# запустити контейнер так, що директорію хоста /host/dir буде змонтовано як /container/dir
docker run -v /host/dir:/container/dir debian

# слідкувати за логами демона в контейнері
docker logs -f ecstatic_lovelace

# збудувати образ з Dockerfile поточної директорії
docker build -t ouruser/ourrepo .

Filed under: Інструменти, Кодерство Tagged: linux

Випадковий ідентифікатор в Python

Можна отримати так:

import random
def random_id(length=6):
   return ''.join(
        random.choice(string.lowercase)
        for i in range(length)
    )

###############
>>> random_id()
'kqxmua'

Якщо треба особливо оформлений, як от IP, чи MAC-адреса, то можна зробити перетворення:

def asmac(val):
    """Convert a byte string to a MAC address string.  """
    return ':'.join('%02X' % ord(c) for c in val)

def random_mac():
    return asmac(random_id())

###################
>>> random_mac()
'78:71:6A:72:6E:63'

Але такі ідентифікатори як “kqxmua” нормальній людині важко запам’ятати, бо вони не асоціюються з жодними поняттями. Ну окрім частинки “ua”, але й то вона туди випадково потрапила. Проте, в Linux можна легко отримати випадкове слово, бо в ньому є словник:

def random_word():
    return random.choice(
        open('/usr/share/dict/words').readlines() # жертиме пам’ять! 
    ).strip()

#################
'.join(random_word() for i in range(5))
'hermitical, Canter, Paryavi, mergences, Mind'

Хоча я знайомий лише з “hermitical” та “mind”, але асоціації вже легше побудувати, правда?


Filed under: Всяке, Кодерство Tagged: linux, Python