Плюсы и минусы docker

Компоненты Docker

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

  1. Docker-демон (Docker-daemon) — сервер контейнеров, входящий в состав программных средств Docker. Демон управляет Docker-объектами (сети, хранилища, образы и контейнеры). Демон также может связываться с другими демонами для управления сервисами Docker.
  2. Docker-клиент (Docker-client / CLI) — интерфейс взаимодействия пользователя с Docker-демоном. Клиент и Демон — важнейшие компоненты «движка» Докера (Docker Engine). Клиент Docker может взаимодействовать с несколькими демонами.
  3. Docker-образ (Docker-image) — файл, включающий зависимости, сведения, конфигурацию для дальнейшего развертывания и инициализации контейнера.
  4. Docker-файл (Docker-file) — описание правил по сборке образа, в котором первая строка указывает на базовый образ. Последующие команды выполняют копирование файлов и установку программ для создания определенной среды для разработки.
  5. Docker-контейнер (Docker-container) — это легкий, автономный исполняемый пакет программного обеспечения, который включает в себя все необходимое для запуска приложения: код, среду выполнения, системные инструменты, системные библиотеки и настройки.
  6. Том (Volume) — эмуляция файловой системы для осуществления операций чтения и записи. Она создается автоматически с контейнером, поскольку некоторые приложения осуществляют сохранение данных.
  7. Реестр (Docker-registry) — зарезервированный сервер, используемый для хранения docker-образов.
    Примеры реестров:
    • Центр Docker — реестр, используемый для загрузки docker-image. Он обеспечивает их размещение и интеграцию с GitHub и Bitbucket.
    • Контейнеры Azure — предназначен для работы с образами и их компонентами в директории Azure (Azure Active Directory).
    • Доверенный реестр Docker или DTR — служба docker-реестра для инсталляции на локальном компьютере или сети компании.
  8. Docker-хаб (Docker-hub) или хранилище данных — репозиторий, предназначенный для хранения образов с различным программным обеспечением. Наличие готовых элементов влияет на скорость разработки.
  9. Docker-хост (Docker-host) — машинная среда для запуска контейнеров с программным обеспечением.
  10. Docker-сети (Docker-networks) — применяются для организации сетевого интерфейса между приложениями, развернутыми в контейнерах.

Установка

Чтобы начать пользоваться Докером, необходимо установить движок — Docker Engine. На странице https://docs.docker.com/engine/install/ доступны ссылки для скачивания под все популярные платформы. Выберите вашу и установите Докер.

В работе Докера есть одна деталь, которую важно знать при установке на Mac и Linux. По умолчанию Докер работает через unix сокет

В целях безопасности сокет закрыт для пользователей, не входящих в группу docker. И хотя установщик добавляет текущего пользователя в эту группу автоматически, Докер сразу не заработает. Дело в том, что если пользователь меняет группу сам себе, то ничего не изменится до тех пор, пока пользователь не перелогинится. Такова особенность работы ядра. Для проверки того, в какие группы входит ваш пользователь, можно набрать команду .

Проверить успешность установки можно командой :

Она выдаёт довольно много информации о конфигурации самого Докера и статистику работы.

Взаимодействие с другими частями системы

Запускать изолированный контейнер, который живет весь внутри себя — малополезно. Как правило, контейнеру нужно взаимодействовать с внешним миром, принимать входящие запросы на определенный порт, выполнять запросы на другие сервисы, читать общие файлы и писать в них. Все эти возможности настраиваются при создании контейнера.

Interactive mode

Самый простой вариант использования Докера, как мы уже убедились — поднять контейнер и выполнить внутри него какую-либо команду:

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

Дело в том, что запускает интерактивную сессию внутри контейнера. Для взаимодействия с ней нужно оставить открытым поток STDIN и запустить TTY (псевдо-терминал). Поэтому для запуска интерактивных сессий нужно не забыть добавить опции и . Как правило их добавляют сразу вместе как . Поэтому правильный способ запуска баша выглядит так: .

Ports

Если запустить nginx такой командой , то nginx не сможет принять ни один запрос, несмотря на то, что внутри контейнера он слушает 80 порт (напомню, что каждый контейнер по умолчанию живет в своей собственной сети). Но если запустить его так , то nginx начнет отвечать на порту 8080.

Флаг позволяет описывать как и какой порт выставить наружу. Формат записи расшифровывается так: пробросить порт 8080 снаружи контейнера в контейнер на порт 80. Причем, по умолчанию, порт 8080 слушается на , то есть на всех доступных интерфейсах. Поэтому запущенный таким образом контейнер доступен не только через , но и снаружи машины (если доступ не запрещен как-нибудь еще). Если нужно выполнить проброс только на loopback, то команда меняется на такую: .

Docker позволяет пробрасывать столько портов, сколько нужно. Например, в случае nginx часто требуется использовать и порт, и для HTTPS. Сделать это можно так: Про остальные способы пробрасывать порты можно прочитать в официальной документации.

Volumes

Другая частая задача связана с доступом к основной файловой системе. Например, при старте nginx-контейнера ему можно указать конфигурацию, лежащую на основной фс. Докер прокинет её во внутреннюю фс, и nginx сможет её читать и использовать.

При работе с Volumes есть несколько важных правил, которые надо знать:

  • Путь до файла во внешней системе должен быть абсолютным.
  • Если внутренний путь (то, что идет после ) не существует, то Докер создаст все необходимые директории и файлы. Если существует, то заменит старое тем, что было проброшено.

Кроме пробрасывания части фс снаружи, Докер предоставляет еще несколько вариантов создания и использования Volumes. Подробнее — в официальной документации.

Переменные окружения

Конфигурирование приложения внутри контейнера, как правило, осуществляется с помощью переменных окружения в соответствии с 12factors. Существует два способа их установки:

  • Флаг . Используется он так:
  • Специальный файл, содержащий определения переменных окружения, который пробрасывается внутрь контейнера опцией .

Плюсы контейнеризации docker

Абстрагирование приложение  от хоста

Задумка в контейнера – полная стандартизация. Контейнер соединяется с хостом определенным интерфейсом, контейнеризорованное приложение не зависит от архитектуры или ресурсов хоста. Для хоста же контейнер некий «черный ящик», не имеет значение что в нем.

Маштабирование

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

Управление версиями и зависимостями

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

Изолирование среды

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

Использование слоев

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

Компоновка

Возможность написания задания конкретных действий доя создания нового образа. Т.е. настройки среды можно писат как код и сохранять в системе контроля версий.

Что такое Docker

Docker — это программное обеспечение, которое дает возможность на определенном участке памяти изолированно установить необходимую ОС (операционную систему), версию Java, настроить переменные окружения, установить различные зависимости и дать доступ только при определенных условиях. При этом данную программу совершенно не будет волновать, что происходит вокруг.

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

Плюсы и минусы инструмента

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

Преимущества

Начать лучше с преимуществ. Среди них выделяют:

  1. Открытый исходный код. Разработчики делятся друг с другом собственными разработками на специализированных площадках.
  2. Низкий уровень потребления ресурсов. Изолированные пространства не предусматривают виртуализацию всей ОС. Они задействуют исключительно ядро хоста, после чего изолируют нужное приложение на уровне процессов.
  3. Скорость развертывания. Она достаточно быстрая. Можно использовать базовый образ Docker для дальнейшей работы.
  4. Простое скрытие процессов. Контейнеризация – то, что позволяет использовать всевозможные средства и способы обработки информации. Фоновые процессы легко скрываются от «посторонних глаз».
  5. Поддержка работы с небезопасными кодами. Технологии изолирования позволяют запускать совершенно разные машинные коды. За целостность приложений переживать не нужно. Связано это с тем, что за счет Докер контейнера осуществляется изоляция утилиты.
  6. Простое масштабирование. Проекты могут расширяться только за счет того, что пользователь решил устанавливать новые контейнеры.
  7. Простой и удобный запуск. Docker Run предоставляет возможность активации любой утилиты внутри изолированного пространства в пределах одного и того же хоста.

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

Недостатки

Управлять образами и контейнерами при определенной сноровке достаточно легко. Хотя кажется, что соответствующий софт не имеет недостатков, это вовсе не так.

Разработчики указывают на то, что Докер требует грамотного обращения. А еще – наличия элементарных навыков программирования, ведь внутри контейнера находится именно код. И его предстоит корректировать под собственные нужды.

В остальном минусов у подобного инструментария нет. Это – отличное решение, которое, в отличие от виртуальных машин, не требует особых ресурсов от используемого устройства.

Сравнение контейнеров Docker с виртуальными машинами

На рисунке 2-3 показано сравнение между виртуальными машинами и контейнерами Docker.

Виртуальные машины Контейнеры Docker
Виртуальные машины содержат приложение, необходимые библиотеки или двоичные файлы и всю операционную систему. Полная виртуализация требует больше ресурсов, чем создание контейнеров. Контейнеры включают в себя приложение и все его зависимости. Но они используют ядро ОС совместно с другими контейнерами, которые выполняются в изолированных процессах в пользовательском пространстве операционной системы узла. (Это не относится к контейнерам Hyper-V, где каждый контейнер запускается на отдельной виртуальной машине.)

Рис. 2-3. Сравнение традиционных виртуальных машин с контейнерами Docker

Для виртуальных машин на сервере узла создается три базовых уровня: самый нижний инфраструктурный слой; затем операционная система узла и низкоуровневая оболочка; и поверх этого каждая виртуальная машина использует собственную ОС и все необходимые библиотеки. Для Docker сервер узла предоставляет только инфраструктуру и операционную систему, а также ядро контейнеров, которое изолирует контейнер с использованием базовых служб операционной системы.

Так как контейнеры требуют гораздо меньше ресурсов (например, им не нужна полная ОС), их проще развертывать и они быстрее запускаются. Это позволяет повысить плотность развертываний, то есть запустить на одной единице оборудования больше служб и сократить затраты на них.

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

Основная цель образа — привести среду (зависимости) к единообразию в различных развертываниях. Это означает, что вы можете отладить образ на одном компьютере, а затем развернуть его на другом компьютере и получить ту же среду.

Образ контейнера — это способ упаковки приложения или службы для надежного и воспроизводимого развертывания. Можно сказать, что Docker является не только технологией, но еще философией и процессом.

При работе с Docker разработчики никогда не жалуются, что приложение работает только на локальном компьютере, но не в рабочей среде. Им достаточно сказать «Выполняется в Docker», так как упакованное приложение Docker будет выполняться в любой поддерживаемой среде Docker. Оно будет работать одинаково во всех сценариях развертывания (разработка, контроль качества, промежуточное размещение и рабочая среда).

Терминология – что нужно знать

Изучение любой утилиты осуществляется с учетом рассмотрения его составляющих. Docker контейнеры работают по определенным принципам, задействовав в процессе самые разные элементы.

Особое значение новичкам необходимо уделить внимание следующим терминам и понятиям:

  1. Демон. Сервер контейнерного типа, который включен в состав программных средств Docker. Является неким «центром управления» объектами – хранилищами, контейнерами, сетями, образами.
  2. Клиент. Пользовательский интерфейс, при помощи которого юзер может взаимодействовать с Daemon. Поддерживает работу одновременно с несколькими демонами.
  3. Образ – своеобразный файл, который содержит конфигурацию, зависимости, а также сведения, необходимые для обеспечения инициализации и развертки containers.
  4. DockerFile – документ, который программерам приходится часто использовать. Содержит правила и принципы сборки образа. Первая строка отвечает за так называемый базовый Image. Остальные команды ответственны за копирование файлов и инициализацию контента для создания среды разработки.
  5. Контейнер – исполняемый пакет рассматриваемого программного обеспечения. Работает без проблем на разных устройствах. Его можно назвать автономным. Содержит в себе все, что может потребоваться при работе с приложениями. Обработка кода возможна, когда присутствует исходный код, среда обработки, системные инструменты и библиотеки с настройками.
  6. Тома – процедуры эмуляции файловых систем. Носят название Volume. Создаются автоматически совместно с Docker контейнерами.
  7. Docker Hub – репозиторий, своеобразное хранилище электронных материалов. Задействуется для хранения образов с разным программным обеспечением.
  8. Реестры – созданные когда-то и зарегистрированные серверы. Нужны для хранения Docker Images.
  9. Host – виртуальная машина (среда), которая используется для запуска контейнеров с программным обеспечением.
  10. Сети – компоненты, применяемые для организации большого количества сетевых интерфейсов в программах контейнерного типа.

Существуют различные Docker-registry. Вот несколько примеров:

  1. Докер Центр – реестр подгрузки образов. Отвечает в полной мере за обеспечение и размещение соответствующих элементов с интеграцией GitHub и BitBucket.
  2. Azure. Изолированные области, задействованные при работе с Images и их элементами в директории Azure.
  3. DTR (доверенные реестры) – службы Докер-реестров. После выполнения оных происходит установка софта на локальных компьютерах и в корпорационных сетках.

Дополнительно рекомендуется рассмотреть иные важные аспекты, связанные с контейнированием. Этот процесс осуществляется в специальной среде. И она имеет собственное название.

Быстрое выкладывание ваших приложений

Docker прекрасно подходит для организации цикла разработки. Docker позволяет разработчикам использовать локальные контейнеры с приложениями и сервисами. Что в последствии позволяет интегрироваться с процессом постоянной интеграции и выкладывания (continuous integration and deployment workflow).

Например, ваши разработчики пишут код локально и делятся своим стеком разработки (набором docker образов) с коллегами. Когда они готовы, отравляют код и контейнеры на тестовую площадку и запускают любые необходимые тесты. С тестовой площадки они могут оправить код и образы на продакшен.

Шаг 6. Мониторинг и диагностика

Эта тема также рассматривается в следующей главе в контексте ИТ-операций, выполняемых в рабочих системах

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

Функции мониторинга и диагностики полностью относятся к сфере DevOps только в том случае, если они выполняются командой разработчиков по отношению к средам тестирования или бета-версиям сред. Для этих целей осуществляются нагрузочное тестирование или мониторинг в бета-версиях сред или средах контроля качества, на которых тестировщики проверяют новые решения.

Назад
Вперед

Даем определение

Docker – это специальное программное обеспечение для той или иной операционной системы. Некий набор инструментов для разработчика, задействованный для автоматизации развертки и управления приложения. Основное предназначение Докера – контейнеризация.

Его можно использовать при:

  • доставке программного обеспечения;
  • разработке приложений;
  • запуске web-программ.

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

Управление контейнерами

Картинка описывает жизненный цикл (конечный автомат) контейнера. Кружками на нём изображены состояния, жирным выделены консольные команды, а квадратиками показывается то, что в реальности выполняется.

Проследите путь команды . Несмотря на то, что команда одна, с точки зрения работы Докера выполняется два действия: создание контейнера и запуск. Существуют и более сложные варианты исполнения, но в этом разделе мы рассмотрим только базовые команды.

Запустим nginx так, чтобы он работал в фоне. Для этого после слова run добавляется флаг :

После выполнения команды Докер выводит идентификатор контейнера и возвращает управление. Убедитесь в том, что nginx работает, открыв в браузере ссылку . В отличие от предыдущего запуска, наш nginx работает в фоне, а значит не видно его вывода (логов). Посмотреть его можно командой , которой нужно передать идентификатор контейнера:

Вы также можете подсоединиться к выводу лога в стиле . Для этого запустите . Теперь лог будет обновляться каждый раз, когда вы обновляете страницу в браузере. Выйти из этого режима можно набрав Ctrl + C, при этом сам контейнер остановлен не будет.

Теперь выведем информацию о запущенных контейнерах командой :

Расшифровка столбиков:

  • CONTAINER_ID — идентификатор контейнера. Так же, как и в git, используется сокращенная запись хеша.
  • IMAGE — имя образа, из которого был поднят контейнер. Если не указан тег, то подразумевается latest.
  • COMMAND — команда, которая выполнилась на самом деле при старте контейнера.
  • CREATED — время создания контейнера
  • STATUS — текущее состояние.
  • PORTS — проброс портов.
  • NAMES — алиас. Докер позволяет кроме идентификатора иметь имя. Так гораздо проще обращаться с контейнером. Если при создании контейнера имя не указано, то Докер самостоятельно его придумывает. В выводе выше как раз такое имя у nginx.

(Команда выводит информацию о том, сколько ресурсов потребляют запущенные контейнеры).

Теперь попробуем остановить контейнер. Выполним команду:

Если попробовать набрать , то там этого контейнера больше нет. Он удален.

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

Теперь выведем все контейнеры командой . Первыми тремя строчками вывода окажутся:

Здесь как раз два последних наших запуска. Если посмотреть на колонку STATUS, то видно, что оба контейнера находятся в состоянии Exited. То есть запущенная команда внутри них выполнилась, и они остановились. Разница лишь в том, что один завершился успешно (0), а второй с ошибкой (127). После остановки контейнер можно даже перезапустить:

Только в этот раз вы не увидите вывод. Чтобы его посмотреть, воспользуйтесь командой .

Подготовка собственного образа

Создание и публикация собственного образа не сложнее его использования. Весь процесс делится на три шага:

  • Создается файл в корне проекта. Внутри описывается процесс создания образа.
  • Выполняется сборка образа командой
  • Выполняется публикация образа в Registry командой

Рассмотрим процесс создания образа на примере упаковки линтера (не забудьте повторить его самостоятельно). В результате сборки, мы получим образ, который можно использовать так:

То есть достаточно запустить контейнер из этого образа, подключив каталог с файлами js для проверки как Volume во внутреннюю директорию /app.

1. Конечная структура директории, на основе файлов которой соберется образ, выглядит так:

Файл eslintrc.yml содержит конфигурацию линтера. Он автоматически прочитывается, если лежит в домашней директории под именем .eslintrc.yml. То есть этот файл должен попасть под таким именем в директорию /root внутрь образа.

2. Создание Dockerfile

Dockerfile имеет довольно простой формат. На каждой строчке указывается инструкция (директива) и её описание.

FROM

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

В корне этого дерева находится образ busybox. В прикладных задачах напрямую его не используют, так как Докером предоставляются подготовленные образы под каждую экосистему и стек.

RUN

Основная инструкция в Dockerfile. Фактически здесь указывается sh команда, которая будет выполнена в рамках окружения, указанного во FROM при сборке образа. Так как по умолчанию всё выполняется от пользователя root, то использовать sudo не нужно (и скорее всего его нет в базовом образе). К тому же учтите, что сборка образа — процесс не интерактивный. В тех ситуациях, когда вы используете команду, которая может запросить что-то от пользователя, необходимо подавлять этот вывод. Например, в случае пакетных менеджеров делают так: . Флаг как раз говорит о том что нужно производиться установку без дополнительных вопросов.

Технически образ Докера — это не один файл, а набор так называемых слоев. Каждый вызов RUN формирует новый слой, который можно представить как набор файлов, созданных и измененных (в том числе удаленных) командой, указанной в RUN. Такой подход позволяет значительно улучшить производительность системы, задействовав кеширование слоев, которые не поменялись. С другой стороны, Докер переиспользует слои в разных образах если они идентичны, что сокращает и скорость загрузки и занимаемое пространство на диске. Тема кеширования слоев довольно важная при активном использовании Докера. Для её эффективной работы нужно понимать как она устроена и как правильно описывать инструкции для максимальной утилизации.

COPY

В соответствии со своим названием команда COPY берет файл или директорию из основной файловой системы и копирует её внутрь образа. У команды есть ограничение. То, что копируется, должно лежать в той же директории, где и Dockerfile. Именно эту команду используют при разработке когда необходимо упаковать приложение внутрь образа.

WORKDIR

Инструкция, устанавливающая рабочую директорию. Все последующие инструкции будут считать, что они выполняются именно внутри неё. По инструкция действует, как команда . Кроме того, когда мы запускаем контейнер, то он также стартует из рабочей директории. Например, запустив bash, вы окажетесь внутри неё.

CMD

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

3. Сборка

Для сборки образа используется команда . С помощью флага передается имя образа, включая имя аккаунта и тег. Как обычно, если не указывать тег, то подставляется latest.

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

  • Зарегистрироваться на Docker Cloud и создать там репозиторий для образа.
  • Залогиниться в cli интерфейсе используя команду .

В бою

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

  1. Скачать новый образ.
  2. Остановить старый контейнер.
  3. Поднять новый контейнер.

Причем, данный порядок действий не зависит от стека технологий. Выполнять деплой можно (как и настройку машин) с помощью Ansible.

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

Заключение

Docker является важным инструментом для каждого современного разработчика, как основа аппаратной виртуализации приложений. Эта технология обладает широким функционалом и возможностями для контроля процессов. Докер позволяет не только развертывать контейнеры, но и оперативно масштабировать их экземпляры, работать с многоконтейнерными приложениями (Docker Compose), а также объединять несколько Докер-хостов в единый кластер (Docker Swarm).

Докер характеризуется достаточно простым синтаксисом. Поэтому он довольно прост в освоении как для  опытных IT-специалистов, так и для новичков. Программное обеспечение совместимо со всеми версиями операционных систем Linux и Windows, поэтому область применения Docker практически не ограничена.

Оцените материал:

Рейтинг
( Пока оценок нет )
Понравилась статья? Поделиться с друзьями:
Все про сервера
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: