SSH-ключи в ансибле с гитхаба

October 24, 2016

Новость номер один. Любой публичный ключ с гитхаба можно узнать по ссылке https://github.com/dhh.keys, где вместо dhh нужно поставить ник нужного пользователя.

Новость номер два. В ансибл есть модуль, который авторизует по ssh с помощью этих ключей.

- authorized_key: user=dhh key=https://github.com/dhh.keys validate_certs=no

"Вау!" - подумал я и тут же накатил ssh-доступ для пользователя vakhov на тестовый сервер. Работает на пять баллов, проблема только в том, что я - avakhov. А кто такой https://github.com/vakhov я не знаю :smile:.

Так, что вау пришлось забрать назад. Открылась большая проблема - пользователей в гитхабе много и любая опечатка в нике будет давать доступ постороннему человеку. Вероятность, что он попробует зайти на ваши сервера конечно изчезающе маленькая, к тому же обычно ssh прячут за VPN, но все равно это неприятно осознавать.

А вы бы воспользовались такой фичей?

Пару приемов при работе с JSON в консоли

October 21, 2016

Если вы работаете курлом с каким-нибудь JSON-API, то кроме умения вбивать без ошибок -H 'Content-Type: application/json вам очень пригодятся утилиты для работы с JSON.

Раньше я всегда использовал питон (обычно он есть везде), чтобы красиво отрисовать результат:

curl ... | python -m json.tool

Но сейчас полностью перешел на jq, очень рекомендую посмотреть, если еще не видели. Вот так например в Селектеле можно найти ID проекта с именем some-proj.

curl -s -H "X-token: $SELECTEL_TOKEN" \
  https://api.selectel.ru/vpc/resell/v2/projects | \
  jq -r '.projects | map(select(.name == "some-proj")) | .[0].id'

Раньше бы я такую штуку сделал бы на руби, но по-моему здорово уметь решать одну и ту же задачу по разному.

Кто дергал этот запрос и послал его?

October 20, 2016

Один из важных моментов оптимизации рейлс-приложения, на котором кстати часто вся оптимизация и заканчивается, это уменьшение количество запросов в базу.

Самый простой способ понять что нужно оптимизировать, пожалуй - посмотреть в ньюрелике под вашим штатным трафиком, какой экшен занимает больше всего времени. После этого запустить приложение локально и начать изучать какие строчки кода тревожат базу. Многие проблемы легко понять просто по логам. Убрав тяжелые N+1, а также большинство запросов CACHE, вы сильно поможете приложению.

Note: Все СACHE очень рекомендую убирать (один из вариантов - смотрим и ностальгируем Caching with Instance Variables), так как несмотря на то что запрос в базу не посылается, ActiveRecord кэширует запрос на уровне SQL, все AR-объекты пересоздаются с нуля, что жгет CPU и негативно влияет на произодительность.

Также есть два удобных инструмента, которые помогут справится с задачей. Старый, добрый rails-footnotes на страницах рисует полную информацию о всех запросах.

Так как футнотес наверняка будет рвать верстку, то я его обычно подключаю с помощью такой конструкции:

defined?(Footnotes) && Footnotes.setup do |f|
  f.enabled = -> { File.exists?(Rails.root.join("FOOTNOTES")) }
end

Теперь, чтобы включить футноты достаточно создать в корне файл touch FOOTNOTES. Если его удалить, то футноты отключатся без перезагрузки сервера. Естественно на продакшене футноты не надо включать никогда :smile:.

Однако футноты не помогут, если таинственный запрос к базе в AJAX'е, а по коду сходу не понятно в каком колбеке или библиотеке он вызывается. Решение тоже есть - это ActiveSupport::Notifications. В инициалайзере подписываемся на SQL-запросы и находим негодяя:

ActiveSupport::Notifications.subscribe "sql.active_record" do |*args|
  puts caller if args[4][:name] == "User Load"
end

Берегите свою базу смолоду и она вам верно прослужит многие годы :smile:.

Утилиты, утилиты

October 19, 2016

Вдруг кто не знал, но есть две прекрасные утилиты, одна полезная, а вторая от авторов Бейзкампа.

Ngrok - позволяет расшарить любой локальный порт прямо в интернет. ngrok http 3000 - просто запомните эту команду. Бесценно, когда нужно показать приложение собеседнику по чатику, потестить его на айпеде, либо отладить работу с веб-хуками.

Xip.io - wildcard-доменное имя к любому IP-адресу - *.10.0.0.1.xip.io resolves to 10.0.0.1. У меня пока не было ни одного кейса, когда бы это пригодилось, но я чувствую, что такие ситуации точно есть.

Какие полезные утилиты для веб-разработки используете вы?

Фейлы, они как файлы, только фейлы

October 18, 2016

В одном из докладов великолепного Алексея Рыбака были слова, что интересны не успехи, а фейлы. И я полностью согласен, что самые теплые воспоминания оставляют именно события, где были тяжелые обстоятельства, да еще и сам оплошал, но в итоге с честью выкрутился. Ну и в принципе все разговоры на вечеринках с коктейлями именно про проблемы, легаси, костыли и синюю изоленту. Я не видел, чтобы два инженерами делились, какая у них большая пользовательская база, как тепло их любят пользователи, какие великолепные показатели и перспективы, ми-ми-ми. Поэтому всяким проблемам, проколам, особенно своим, я всегда уделяю самое пристальное внимание.

Итак сегодня руби и рейлс (примеры выдуманные, но основаны на реальных событиях):

# 1
def calc_full_name
  full_name = [first, last].join(" ") # хе-хе, надо self.full_name = ...
end
# 2
user = User.find(1)
user.json["k"] = "v"
user.save! # хо-хо, нужно user.json_will_change! или переделать
# 3
3/2 == 1 # хы-хы, 1 а не 1.5

Это то что сходу вспомнил, чтобы написать очередной пост :smiley_cat:. Идеи то уже кончаются, а блог то еще писать не меньше 10 месяцев. идет.

Delayed Job - хак или фича?

October 17, 2016

Так сложилось, что мы пользуемся для всяких асинхронных задач DelayedJob. Удобно, что этот тот же постгрес, из коробки комфортный API, разумные дефалтовые настройки (например интервалы сколько раз запускать упавшую задачу).

Однако если задуматься, то DJ - эта та еще штука и при эксплуатации нужно знать его особенности. Когда вы делаете User.find(1).delay.send_hello, то в базу сериализуется объект типа User и факт, что к нему нужно применить метод :send_hello. Одноко, чтобы это работало как магия, при восстановлении AR-объекта, на самом деле он берется прямо из базы psych_ext.rb#L30-L65.

В большинстве случаев это просто работает, но могут быть необычные сайд эффекты. Если метод применяется к объекту, который успели уже удалить, то будет ошибка 500. Или предположим, что пользователь два раза быстро нажал на восстановление пароля, вы сохранили токен восстановления, письмо в очередь, потом токен перетерся новым, еще одно письмо в очередь. Если очередь затупила, то в зависимости от того как реализован метод отправки письма, пользователь может получить либо два письма с разным токеном (первый из них уже не будет работать), либо с самым свежим.

Вообще 99% необычного поведения DJ проявляется, если очередь не успевает обрабатываться, поэтому ее обязательно нужно мониторить. А так можно пользоваться. Работает.

Racket

October 14, 2016

Вот везде говорят, функциональное, функциональное, программирование. Я спросил в нашем чатике, а что в тренде? На чем я должен программировать, если пришел на вечеринку с коктейлями?

От Хаскеля отказался сразу, спасибо не надо. Я несколько раз пытался его понять, даже книжку одну прочитал до половины, они там так и не написали не строчки кода, и я не написал ни строчки кода. Все что знаю - есть монады, морфизмы и эндофункторы. Достаточно. Что тогда остается? Возможные лисп-диалекты: скобочки и сложение в польской записи. Ок. Из ортодоксальных показалось, что самый живой Clojure, но Муслим посоветовал сначала посмотреть на Racket - один из лиспов, который учат в школах, институтах и других учебных заведениях.

Что могу сказать - ну тоже язык. Скобочек много. Мозгу тяжело. Все как положено. Поставляется с забавной визуальной средой DrRacket, вот я написал пример из тьюториала:

Попробовал я эту красоту и сразу представил, как подходит Заказчик и говорит: "кружочки - отличные, а вот этот квадрат сделай плиз желтым".

В руби я бы вставил бы что-нибудь типа x == 1 && y == 0 && kind == "square" ? "yellow" : "red" и пошел бы дальше. Здесь же получилось захачить далеко не сразу. В итоге получилось примерно так:

#lang slideshow

(define (square n)
  (filled-rectangle n n))

(define (series mk)
  (hc-append 4 (mk 5 0) (mk 10 1) (mk 20 2)))

(define (hacked_red i mode)
  (if (and (eq? mode 'with_hacked_red) (eq? i 1)) "yellow" "red"))

(define (color i j mode)
  (cond
    [(eq? j 0) (hacked_red i mode)]
    [(eq? j 1) "green"]
    [else "blue"]))

(define (logo mk mode)
  (vc-append
   (series (lambda (sz i) (colorize (mk sz) (color i 0 mode))))
   (series (lambda (sz i) (colorize (mk sz) (color i 1 mode))))
   (series (lambda (sz i) (colorize (mk sz) (color i 2 mode))))))

(logo circle 'default)
(logo square 'with_hacked_red)

Хм. Выглядит не очень. Кто знает как сделать более красиво и правильно?

Терраформим Селектел

October 13, 2016

Научился терраформить Селетел я далеко не сразу, пришлось даже немного его подебажить и openstack-утилиты. Кстати будете дебажить терраформ, сначала попробуйте запустить его с TF_LOG=1, может быть поможет, так как компилируется он довольно долго, хоть и написан на Го.

Вообще, терраформ - это волшебная утилита для первичного создания серверов, настройки DNS, всевозможных штук в Амазоне и прочих вещей. Это такой ансибл для инфраструктуры, с отличной идеологией. Вы пишите в скрипте: "хочу: чтобы у меня были два таких бокса, соответсвующие днс-записи и бакет на амозоне", терраформ смотрит что у вас есть сейчас и добавляет необходимо, либо удаляет избыточное. Там конечно немного нужно приноровится, но штука совершенно космическая.

Так вот заводите аккаунт в селектеле, добавляете туда проект и вперед:

provider "dnsimple" {
    email = "${var.DNSIMPLE_EMAIL}"
    token = "${var.DNSIMPLE_TOKEN}"
}

provider "openstack" {         
  auth_url  = "https://api.selvpc.ru/identity/v3"
  domain_name = "${var.DOMAIN_NAME}"
  tenant_name = "${var.PROJECT_ID}"
  tenant_id = "${var.PROJECT_ID}"
  user_name  = "${var.USER}"
  password  = "${var.PASSWORD}"
}

variable "external_gateway" {
  default = "ab2264dd-bde8-4a97-b0da-5fea63191019" # hardcoded gw for selectel
}

resource "openstack_networking_network_v2" "network" {
  name = "network"           
  admin_state_up = "true"      
  region = "${var.REGION}"
}

resource "openstack_networking_subnet_v2" "subnet" {
  name = "subnet"            
  network_id = "${openstack_networking_network_v2.network.id}"
  cidr = "10.0.0.0/24"
  ip_version = 4               
  region = "${var.REGION}"              
}

resource "openstack_networking_router_v2" "router" {
  name = "router"
  admin_state_up = "true"
  region = "${var.REGION}"              
  external_gateway = "${var.external_gateway}"
}

resource "openstack_networking_router_interface_v2" "router" {
  router_id = "${openstack_networking_router_v2.router.id}"
  subnet_id = "${openstack_networking_subnet_v2.subnet.id}"
  region = "${var.REGION}"              
}

resource "openstack_compute_keypair_v2" "vakhov" {
  region = "${var.REGION}"              
  name = "vakhov"
  public_key = "..."
}

resource "openstack_blockstorage_volume_v1" "box01" {
  name = "disk-for-box01"
  region = "${var.REGION}"
  size = 150
  image_id = "${var.IMAGE_ID}"
  volume_type = "basic.${var.REGION}a"
}

resource "openstack_compute_instance_v2" "box01" {
  name = "box01"
  flavor_name = "${var.FLAVOR}"
  region = "${var.REGION}"
  key_pair = "vakhov"

  network {
    uuid = "${openstack_networking_network_v2.network.id}"
    floating_ip = "${var.BOX_01_FLOATING_IP}"
  }

  metadata = {
    "x_sel_server_default_addr"="{\"ipv4\":\"\"}"
  }

  block_device {
    boot_index = 0
    uuid = "${openstack_blockstorage_volume_v1.box01.id}"
    source_type = "volume"
    destination_type = "volume"
  }
}

resource "dnsimple_record" "box" {
  domain = "${var.DOMAIN}"
  name = "mega-box"
  value = "${openstack_compute_instance_v2.box01.network.0.floating_ip}"
  type = "A"
  ttl = 600
}

Сорри, что без ката, но по-моему это божественно. Если вы натыкали хотя бы 10 проектов ручками, то оцените :heart_eyes_cat:. Конфигурировать таким образом инфраструктуру не только быстрее, но и гораздо надежнее.

В Селектеле есть несколько хитростей, которые его отличают от стандатного опенстека, описанного в документации:

  1. авторизоваться нужно именно так, как в моем примере;
  2. флеворы в Селектеле создаются под каждый бокс отдельно (можно использовать один флевор для нескольких серверов) и они бездисковые, их можно создать заблаговременно с помощью nova flavor-create как описано здесь;
  3. айдишники образов можно взять с помощью glance image-list или из панели;
  4. и самое главное, добавьте в мета-информацию сервера поле x_sel_server_default_addr = {"ipv4":""}, не спрашивайте зачем, так надо, иначе не будет работать сеть.

Вуаля. Теперь вы можете легко терраформить Селектел как вам нравится. А как вы создаете свою инфраструктуру?

Автоматизация работы с проектами Селектела

October 12, 2016

Я хотел бы возможно написать пафосный туториал, как автоматизировать работу в облаке Селектела, но я не умею их писать. Поэтому скажу вот так - в Селектеле кое-что легко автоматизировать, в некоторых местах есть хитрости, а кое-что сделать очень сложно или совсем нельзя. Думаю, это очень точное описание.

Итак, создать проект, поставить квоты, удалить проект (вот это мы не стали скриптовать, а удаляем через оболочку, где есть дополнительное подтверждение, так как удаляется проект очень-очень быстро и может получиться очень-очень неловко), добавить пользователей - все это прекрасно работает. Список всех вызовов есть в разделе документации (нужно залогинится).

Далее великолепно работают все OpenStack-утилиты, как написано в блоге Селектела, кроме neutron, он почему-то не работает :smile:. Возможно потому-что в 2015 году у них был один регион, а сейчас два. Но нейтрон нам и не понадобится.

Чтобы легче работалось, я завернул утилиты в докер образ и теперь вызываю вот так:

docker run --rm -it \
  -e OS_AUTH_URL=https://api.selvpc.ru/identity/v3 \
  -e OS_PROJECT_ID=... \
  -e OS_USER_DOMAIN_NAME=... \
  -e OS_USERNAME=... \
  -e OS_PASSWORD=... \
  -e OS_REGION_NAME=... \
  uchiru/ostack:v2 glance image-list

USER_DOMAIN_NAME - это аккаунт (номер договора), PROJECT_ID - айди проекта, можно взять в панели (hex на 32 символа), REGION_NAME - 'ru-1' - Санкт-Петербург (в Питере, тире, пить), 'ru-2' - Москва, LOGIN/PASSWORD - логин и пароль вашего пользователя в селектеле, которого нужно добавить в проект.

Вот. Проект вы легко создадите скриптами или через панель, а завтра расскажу, что с ним можно сделать дальше.

Пройти не оставляя следа

October 11, 2016

В современном вебе дорого владеть чем-либо. Представьте у вас есть сервер, клевый, гудит, мигает лампочками. Но это же очень дорого! Теперь вы полностью за него отвечаете, за счета за электричества, за все его поломки, простои и так далее. Арендовать куда-как дешевле с точки зрения поддержки.

Вот сервер гудит в датацентре, за ним смотрят, меняют что-то там, заботяться. Но это все равно все очень дорого: KVM, разметка томов, сгорел блог питания, посыпались диски и прочее.

В облаке гораздо лучшее, пару кликов - и через пару минут ssh-доступ, дистрибутив какой хотите, мощность по заказу. Заходите, ставите пару пакетов...

Стоп, все равно дорого! Лучше ставить пакеты ансиблом, готовить образы пакером, разворачивать контейнеры в ECS, делать все, чтобы владеть как можно меньшим. В современном мире исходники на гитхабе и кредитка в кармане самый выгодный способ поддерживать типовые проекты. Heroku, Amazon, Github, Firebase, PaaS, IaaS, сотни других букв - сейчас есть много способов делать дела, не владея фактически ничем осязаемым, кроме кредитной карты.

И я скажу, что мне это очень нравится. Современные IT-проекты - очень сложная штука и здорово, что вокруг создается полный набор самостоятельных, зрелых технологий, благодаря которым можно развивать ключевую фишку продукта, а за остальное платить. Например мой блог - исходники на гитхабе, комментарии - дискус, хостинг - гитхаб пейджес, и только домен мой. И то я себя ругаю за малодушие, что испугался вести блог на avakhov.github.io. Чтобы хоть как-то скомпенсировать этот факт - перенес домен с GoDaddy на Dnsimple. Владеть доменом в простом интерфейсе днсимпла психологически дешевле.

PS. еще для ежедневной публикации постов приходится держать целый DigitalOcean дроплет за 5$ в месяц, единственная задача которого выполнить крон-задачу в 7:23 каждый день. Не знаю как это сделать проще :(