Showing posts with label разработка. Show all posts
Showing posts with label разработка. Show all posts

Thursday, June 25, 2009

Мониторинг лог-файлов с использованием Zabbix

Программный комплекс Zabbix (www.zabbix.com) - популярное средство для мониторинга компьютерных систем. Zabbix позволяет отслеживать различные системные и прикладные показатели, например, загруженность CPU и сетевых интерфейсов, скорость работы сайтов, размер пула соединений и др. При возникновении внештатных ситуаций можно настроить уведомление по различным каналам вроде емейл и SMS.

Часто возникает необходимость мониторинга лог-файлов. А именно, при возникновении ошибочной ситуации было бы полезно отсылать письмо или SMS. В версии 1.4 Zabbix нет встроенных средств, которые бы позволяли такое сделать. Мне пришлось для этого написать небольшой скрипт на bash, который потом подключить как пользовательский параметр в Zabbix. Скрипт можно скачать здесь diglog.sh

Для подключения скрипта в Zabbix нужно выполнить следующее:

1. Прописать в zabbix-agentd.conf строку для определения нового пользовательского параметра: UserParameter=diglog[*],/etc/zabbix/alert.d/diglog.sh $1 $2 После чего перезапустить zabbix-agent

2. Через веб-интерфейс Zabbix добавить новый Item. Ключ (key) у него должен быть, например, таким diglog[/home/application/application.log,/tmp/application.log.diglog] Первый параметр - это путь к лог-файлу, который нужно мониторить. Второй параметр - путь к файлу состояния, в котором будет хранится текущее положение в лог-файле. Файл состояния должен быть свой для каждого лог-файла.
Тип у Item'а должен быть Character по крайней мере для Zabbix 1.4.
После добавления Zabbix сразу же начнет выбирать по одной строчки из лог-файла.

3. Добавить триггер для обработку ошибок в лог-файле, например, с таким выражением: {ZABBIX Server:diglog[/home/application/application.log,/tmp/application.logdiglog].str(ERROR)}=1
4. Подключить к триггеру действия для отсылки уведомления об ошибке.

Wednesday, February 4, 2009

Collection.addAll в Java Generics

Родовые типы (generics) в Java - одна из самых интересных и полезных возможностей. Она очень грамотно и логично спроектирована, но иногда попадаются моменты, которые ставят в тупик. Лично я пару натыкался на такой фрагмент:
List<? extends MyType> l1; List<? extends MyType> l2; l1.addAll(l2); Компилятор при этом выдает ошибку в строке, где элементы одной коллекции добавляются в другую. Казалось бы, в чем проблема - коллекции типизированы одинаково, следовательно, могут содержать одинаковые элементы. На самом деле, если бы такой код был допустимым, нарушалась бы безопасность типов (в коллекцию можно было бы добавить недопустимые элементы). Это демонстрирует следующий пример:
List<Double> dl = null; List<Integer> il = null; List<? extends Number> l1 = dl; List<? extends Number> l2 = il; l1.addAll(l2); В коллекцию Double попадают объекты типа Integer.

Thursday, October 23, 2008

Интересный способ мониторинга рабочих сайтов

Иногда возникает ситуация, когда рабочий (production) сайт начинает тормозить, или вообще подвисать. Для нахождения проблемы можно просмотреть код в поисках узких мест или написать нагрузочный тест и посмотреть профилировщиком методы, которые выполняются много времени.

Но часто бывает так, что сайт начинает тормозить только при некотором стечении обстоятельств, например, когда заходит определенный пользователь на определенную страницу. Такого рода узкие места нагрузочным тестом не покроешь, так как не ясно, на каких именно данных проявляется проблема.

В этом случае можно воспользоваться утилитой jstack которая входит в состав JDK начиная с версии 1.5.0. Эта утилита позволяет просмотреть текущее состояние работающих в указанной виртуальной машине потоков (которые thread). Например,
user@laptop$ jstack 3344 "btpool0-439" prio=10 tid=0xa98da000 nid=0x1b3a in Object.wait() [0xa99ff000..0xa99ff130] java.lang.Thread.State: TIMED_WAITING (on object monitor) at java.lang.Object.wait(Native Method) at org.mortbay.thread.BoundedThreadPool$PoolThread.run(BoundedThreadPool.java:482) - locked <0xb47e0078> (a org.mortbay.thread.BoundedThreadPool$PoolThread) "Java2D Disposer" daemon prio=10 tid=0xaa35c800 nid=0xb2e in Object.wait() [0xa9fd4000..0xa9fd50b0] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:116) - locked <0xaf007a30> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:132) at sun.java2d.Disposer.run(Disposer.java:125) at java.lang.Thread.run(Thread.java:619) Для каждого потока показаны его атрибуты, в том числе его название, и стектрейс.

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

Например, очевидными кандидатами на добавление в название потока являются адрес страницы, которая обрабатывается и логин пользователя. Код по добавлению этой информации логично разместить в HTTP-фильтре. Фрагмент его будет выглядеть приблизительно так:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String threadName = getUser().login + ": " + RequestInfo.get().getRequestUrl(); Thread.currentThread().setName(threadName); chain.doFilter(request, response); } Теперь, если в выводе jstack будет часто видно, например, такое
"medved: /heavy/calculation" prio=10 tid=0x9aa9a800 nid=0x2519 runnable [0x9ad18000..0x9ad 190b0] java.lang.Thread.State: RUNNABLE at com.site.heavy.calculation.Calc.inner(Calc.java:113) at com.site.heavy.calculation.Calc.outer(Calc.java:43) значит, для пользователя medved медленно показывается страница /heavy/calculation. Это уже можно профилировать.

Названия потока удобно использовать также в других случаях отладки, например, записать в него User-Agent, и тогда прямо в стектрейсе ошибки будет видно, под каким браузером она возникла.

Thursday, October 9, 2008

Устанавливаем на Amazon EC2 и Amazon EBS Java-приложение с MySQL

На примере типичного сайта опишу последовательность действий по установке на хостинг Amazon.

Приложение - сайт на Java, который требует большой вычислительной мощности. Поэтому было принято решение сразу запускаться на Amazon. В работе сайт использует на чтение-запись базу MySQL. Сайт будет развернут на одном экземпляре (instance) Elastic Computing Cloud (EC2). База MySQL - на томе (volume) Elastic Block Storage (EBS). Все пользовательские данные будут храниться в MySQL. На диске экземпляра будут храниться настройки, а также скрипт сборки приложения, который будет собирать приложения из внешних источников (например, из репозитария Subversion).

Далее в тексте, при описании вводимых команд, если приглашение оболочки начинается на $ - команду следует выполнять на локальном компьютере, если приглашение начинается на # - на удаленном экземпляре EC2.

Установка утилит EC2 и настройка окружения

  1. Заходим на http://aws-portal.amazon.com, логинимся, покупаем EC2. На страницах нужно будет найти и запомнить Access Key ID, Secret Access Key и Account ID. Также нужно сгенерировать и сохранить файл с сертификатом X.509 (cert-11111111111111.pem) и файл с приватным ключем (pk-11111111111111.pem).
  2. Устанавливаем Amazon EC2 Command-Line Tools.
    1. Скачиваем с Amazon.
    2. Распаковываем в нужное место (например, /usr/ec2).
    3. Прописываем окружение (вариант для Linux): pupkin@local$ export EC2_HOME=/usr/ec2/ec2-api-tools-1.3-19403 pupkin@local$ export PATH=$PATH:$EC2_HOME/bin pupkin@local$ export EC2_PRIVATE_KEY=/home/pupkin/mega-project/ec2/pk-11111111111111.pem pupkin@local$ export EC2_CERT=/home/pupkin/mega-project/ec2/cert-11111111111111.pem
    4. Проверить, правильно ли установлены утилиты можно, выполнив команду ec2-describe-images. Результатом команды будет список доступных для запуска образов виртуальных машин (AMI): pupkin@local$ ec2-describe-images -a IMAGE ami-5586623c 1fm2knh6gyecefa34102-phe-bucket/image.manifest.xml 437711880492 available public i386 machine IMAGE ami-63ec090a 1VYRBPTAGB3GRJD3N902-AMI-082407/image.manifest.xml 681122512140 available public i386 machine IMAGE ami-18cf2a71 1VYRBPTAGB3GRJD3N902-AMI-110407-dev/image.manifest.xml 681122512140 available public i386 machine ...

Настройка экземпляра

В качестве ОС выбираем Ubuntu Linux. За основу берем образ Ubuntu 8.04 LTS Hardy от alestic.com. Дальше доставим туда необходимые Java, MySQL и всё остальное.
  1. Запускаем экземпляр с нужным образом.
    1. Ищем последний по дате нужный образ. Выбираем base (лишнего нам не надо) и 64-битную платформу (нашему сайту будет нужно много памяти). pupkin@local$ ec2-describe-images -a |grep ubuntu |grep alestic |grep 64 IMAGE ami-17d7337e alestic-64/ubuntu-8.04-hardy-base-64-20080924.manifest.xml
    2. Генерируем ключик для доступа к экземпляру по SSH pupkin@local$ ec2-add-keypair pstam-keypair и сохраняем его в файле, например, /home/pupkin/mega-project/ec2/id_rsa-pstam-keypair.
      Если используется OpenSSH, для этого файлы нужно выставить правильные права доступа: pupkin@local$ chmod 600 /home/pupkin/mega-project/ec2/id_rsa-pstam-keypair
    3. Запускаем выбранный экземпляр pupkin@local$ ec2-run-instances ami-17d7337e -k pstam-keypair -t m1.large RESERVATION r-4073a129 701161471898 default INSTANCE i-1b903272 ami-17d7337e pending pstam-keypair 0 m1.large 2008-10-02T12:54:37+0000 us-east-1b aki-b51cf9dc ari-b31cf9da i-1b903272 - это идентификатор экземпляра. Он будет нужен позднее.
      pending - означает, что экземпляр в процессе запуска.

      Ждем 15 секунд, и выполняем команду pupkin@local$ ec2-describe-instances RESERVATION r-4073a129 701161471898 default INSTANCE i-1b903272 ami-17d7337e ec2-75-101-241-247.compute-1.amazonaws.com domU-12-31-39-01-59-41.compute-1.internal running pstam-keypair Статус должен поменяться на running. Если не поменялся - нужно подождать еще.
      Когда статус поменяется на running, в таблице будет видно внешний (ec2-75-101-241-247.compute-1.amazonaws.com) и внутренний (domU-12-31-39-01-59-41.compute-1.internal) адреса экземпляра. Внешний адрес используется для доступа к экземляру из интернета, внутренний - с других инстансов (если такие будут).
  2. Открываем доступ к экземпляру по нужным портам: pupkin@local$ ec2-authorize default -p 22 pupkin@local$ ec2-authorize default -p 80 проверяем pupkin@local$ telnet ec2-75-101-241-247.compute-1.amazonaws.com 22
  3. Логинимся на экземпляр и устанавливаем нужные пакеты pupkin@local$ ssh -i /home/pupkin/mega-project/ec2/id_rsa-pstam-keypair root@ec2-75-101-241-247.compute-1.amazonaws.com root@domU-12-31-39-01-59-41# apt-get update root@domU-12-31-39-01-59-41# apt-get install mysql-server root@domU-12-31-39-01-59-41# apt-get install apache2 root@domU-12-31-39-01-59-41# apt-get install sun-java6-jdk root@domU-12-31-39-01-59-41# apt-get install zabbix-agent root@domU-12-31-39-01-59-41# apt-get install subversion root@domU-12-31-39-01-59-41# apt-get install postfix root@domU-12-31-39-01-59-41# apt-get install mailx Дополнительные пакеты можно будет поставить и позднее, при необходимости.

Настройка Elastic IP

При каждом старте экземпляре ему выдается новый IP-адрес. Чтобы при этом избежать проблем с DNS-ом, закажем также Elastic IP. pupkin@local$ ec2-allocate-address ADDRESS 75.101.137.69 pupkin@local$ ec2-associate-address -i i-1b903272 75.101.137.69 ADDRESS 75.101.137.69 i-1b903272 - это идентификатор экземляра. Если вы его забыли, можно посмотреть командой ec2-describe-instances). Теперь этот адрес можно (и нужно) прописать в DNS, например, связав с именем http://mega-project.pupkin.com.

Настройка EBS

  1. Создаем том. pupkin@local$ ec2-create-volume -s 200 -z us-east-1b us-east-1b - зона, или датацентр Amazon. Её нужно выбрать такой же, в какой запущен экземпляр, чтобы не гонять лишний трафик. Посмотреть зону экземпляра можно командой ec2-describe-instances. Ждем пока поднимется, смотрим информацию о томах: pupkin@local$ ec2-describe-volumes VOLUME vol-2a08ed43 200 us-east-1b available 2008-10-02T15:50:14+0000
  2. Подключаем том к инстансу pupkin@local$ ec2-attach-volume -i i-1b903272 vol-2a08ed43 -d /dev/sdd1
  3. Для новосозданного тома создаём и монтируем файловую систему на экземпляре pupkin@local$ ssh -i /home/pupkin/mega-project/ec2/id_rsa-pstam-keypair root@ec2-75-101-241-247.compute-1.amazonaws.com root@domU-12-31-39-01-59-41# mkfs -t ext3 /dev/sdd1 root@domU-12-31-39-01-59-41# mkdir /mnt/db root@domU-12-31-39-01-59-41# mount /dev/sdd1 /mnt/db Монтирование /dev/sdd1 в /mnt/db нужно прописать в /etc/fstab. Тогда при перезапуске экземпляра том примонтируется автоматически.

Настройка ПО

  1. MySQL MySQL нужно перенести из папки по умолчание (/var) на EBS в раздел с данными (мы его смонтировали в /mnt/db).
    1. Останавливаем MySQL. root@domU-12-31-39-01-59-41# /etc/init.d/mysql stop * Stopping MySQL database server mysqld
    2. Копируем уже созданную БД. root@domU-12-31-39-01-59-41# mkdir /mnt/db/lib /mnt/db/log root@domU-12-31-39-01-59-41# mv /var/lib/mysql /mnt/db/lib/ root@domU-12-31-39-01-59-41# mv /var/log/mysql /mnt/db/log/ root@domU-12-31-39-01-59-41# test -f /vol/log/mysql/mysql-bin.index && perl -pi -e 's%/var/log/%/vol/log/%' /vol/log/mysql/mysql-bin.index
    3. Правим конфиг. root@domU-12-31-39-01-59-41# vim /etc/mysql/my.cnf Раздел mysqld должен быть таким [mysqld] innodb_file_per_table datadir = /mnt/db/lib/mysql log_bin = /mnt/db/log/mysql/mysql-bin.log max_binlog_size = 1000M log_slow_queries = /mnt/db/log/mysql/mysql-slow.log long_query_time = 10
    4. Запускаем. root@domU-12-31-39-01-59-41# /etc/init.d/mysql start
  2. Собственно сайт
    1. Создаём пользователя, под которым будет работать сайт (если сайт сделан в виде отдельного процесса). root@domU-12-31-39-01-59-41# /usr/sbin/adduser mega-project
    2. Пишем скрипт сборки, который
      • чекаутит последнюю версию сайта из репозитария Subversion;
      • делает сборку;
      • перезапускает сайт из новособранной версии.
    3. Запускаем скрипт, настраиваем конфиги приложения, запускаем приложение. Теперь можно зайти на http://mega-project.pupkin.com/ и посмотреть на работающий сайт :)

Сохраняем образ тома

Чтобы не потерялась проделанная работа, нужно сохранить образ настроеного экземпляра. Сохраненный образ также пригодится, если экземпляр нужно будет перезапустить, для поднятия кластера и др.
  1. Копируем ключи и сертификат - они будут нужны на экземпляре. pupkin@local$ cd /home/pupkin/mega-project/ec2/id_rsa-pstam-keypair pupkin@local$ scp -i id_rsa-pstam-keypair id_rsa-pstam-keypair root@ec2-75-101-241-247.compute-1.amazonaws.com: pupkin@local$ scp -i id_rsa-pstam-keypair pk-11111111111111.pem root@ec2-75-101-241-247.compute-1.amazonaws.com: pupkin@local$ scp -i id_rsa-pstam-keypair cert-11111111111111.pem root@ec2-75-101-241-247.compute-1.amazonaws.com:
  2. Собираем пакет с файлами root@domU-12-31-39-01-59-41# ec2-bundle-vol -e /tmp -d /mnt -k pk-11111111111111.pem -c cert-11111111111111.pem -u <Account ID без символов -> -p mega-project-image -s 1500 1500 - размер образа. На стоимость хранения не оказывает влияния, но при небольших размерах быстрее архивируется (перед хранением) и быстрее стартует экземпляр.
    Размер образа должен быть не меньше, чем размер примонтированной на / файловой системы.
    Если значение размера будет слишком маленьким, команда ec2-bundle-vol может выдать сообщение об ошибке execution failed: "rsync ... "
  3. Заливаем собранный пакет в Amazon Structured Storage Service (S3). root@domU-12-31-39-01-59-41# ec2-upload-bundle -b pupkin-images -m /mnt/mega-project-image.manifest.xml -a <Access Key Id> -s <Secret Access Key>
  4. С локальной машины регистрируем пакет как образ.
  5. pupkin@local$ ec2-register pupkin-images/mega-project-images.manifest.xml Всё, дело сделано :).

Возникшие проблемы и вопросы

  1. Почему-то MySQL сразу при старте начинает сильно загружать процессор. Полечилось перезапуском MySQL.
UPD. При переносе данных MySQL в другую папку нужно также поправить настройки apparmor (в /etc/apparmor.d/usr.sbin.mysqld) и logrotate (в /etc/logrotate.d/mysql-server)

Friday, May 16, 2008

OpenAds и русские буквы

Уже достаточно долго для показа баннеров я использую баннеровертелку OpenX (раньше называлась OpenAds, http://www.openx.org/). Для интеграции с сайтами, которые не используют PHP (а большинство у меня на Java), в OpenX используется XML-RPC.

Ну почему софт на PHP всегда такой глючный!

XML-RPC сервер в OpenX всегда возвращает ответ в кодировке UTF-8. Однако среди поддерживаемых OpenX кодировок нет UTF-8. Для русских символом есть koi8r и windows-1251. И когда в описании баннера присутствуют русские символы (в моем случае - в windows-1251), они не преобразовываются в UTF-8. В результате мой XML-RPC клиент от Apache выдает сообщение:
org.apache.xmlrpc.client.XmlRpcClientException: Failed to read servers response: Invalid byte 2 of 2-byte UTF-8 sequence.
at org.apache.xmlrpc.client.XmlRpcStreamTransport.readResponse(XmlRpcStreamTransport.java:179)
at org.apache.xmlrpc.client.XmlRpcStreamTransport.sendRequest(XmlRpcStreamTransport.java:145)
at org.apache.xmlrpc.client.XmlRpcHttpTransport.sendRequest(XmlRpcHttpTransport.java:94)
      ...
В списке рассылки Apache XML-RPC пользователи жалуются на похожую проблему, на что разработчики XML-RPC резонно отвечают, что это проблема сервера, и решать ее они в принципе не будут. Но баннеры нужно показать. Поэтому правим немного XML-RPC клиент. В метод
org.apache.xmlrpc.client.XmlRpcStreamTransport.readResponse
ставим первую строку
InputSource isource = new InputSource(ResponseFilter.filter(pStream));
и сам клас ResponseFilter
public class ResponseFilter {
  public static InputStream filter(InputStream input) {
      StringWriter s = new StringWriter();
    
      try {
          InputStreamReader r = new InputStreamReader(input, "windows-1251");
        
          int ch;
          while ((ch = r.read()) != -1) {
              s.write(ch);
          }
        
          input.close();
      } catch (IOException e) {
          throw new RuntimeException(e);
      }
    
      return new ByteArrayInputStream(s.toString().getBytes());
  }
}
Теперь работает.