2016年3月28日月曜日

OpenStackのCLIでCinderボリュームを作成する

事前準備

次の記事を参照し、Python APIの導入と環境変数のセットまで終わらせておいてください。

OpenStack Python APIでCinderボリュームを作成する

OpenStackのCLI(コマンドラインインタフェース)はPython APIを導入すると利用できるようになります。CLIを実行するとPython APIを呼び出すようになっています。


ボリュームを作成する

次のコマンドを実行して、1GBのボリュームを作成してみましょう。「matsuos-vol1」という名前のボリュームが1つ作成されます。

$ cinder create 1 --display-name matsuos-vol1


ubuntu@matsuos-cluster-1:~/openstack$ cinder create 1 --display-name matsuos-vol1
+---------------------+--------------------------------------+
|       Property      |                Value                 |
+---------------------+--------------------------------------+
|     attachments     |                  []                  |
|  availability_zone  |                 nova                 |
|       bootable      |                false                 |
|      created_at     |      2016-03-27T20:50:56.207319      |
| display_description |                 None                 |
|     display_name    |             matsuos-vol1             |
|      encrypted      |                False                 |
|          id         | 924e30b2-4379-4594-a403-1463055c6ecf |
|       metadata      |                  {}                  |
|     multiattach     |                false                 |
|         size        |                  1                   |
|     snapshot_id     |                 None                 |
|     source_volid    |                 None                 |
|        status       |               creating               |
|     volume_type     |                 None                 |
+---------------------+--------------------------------------+



次のコマンドを実行して、ボリュームの一覧を表示します。

$ cinder list

ubuntu@matsuos-cluster-1:~/openstack$ cinder list
+--------------------------------------+-----------+--------------+------+-------------+----------+-------------+
|                  ID                  |   Status  | Display Name | Size | Volume Type | Bootable | Attached to |
+--------------------------------------+-----------+--------------+------+-------------+----------+-------------+
| 924e30b2-4379-4594-a403-1463055c6ecf | available | matsuos-vol1 |  1   |     None    |  false   |             |
+--------------------------------------+-----------+--------------+------+-------------+----------+-------------+

次のコマンドを実行して、ボリュームの詳細情報を表示します。

$ cinder show <volume id>

ubuntu@matsuos-cluster-1:~/openstack$ cinder show 924e30b2-4379-4594-a403-1463055c6ecf
+---------------------------------------+--------------------------------------+
|                Property               |                Value                 |
+---------------------------------------+--------------------------------------+
|              attachments              |                  []                  |
|           availability_zone           |                 nova                 |
|                bootable               |                false                 |
|               created_at              |      2016-03-27T20:50:56.000000      |
|          display_description          |                 None                 |
|              display_name             |             matsuos-vol1             |
|               encrypted               |                False                 |
|                   id                  | 924e30b2-4379-4594-a403-1463055c6ecf |
|                metadata               |                  {}                  |
|              multiattach              |                false                 |
|         os-vol-host-attr:host         |    ds0015@rbd_volumes#rbd_volumes    |
|     os-vol-mig-status-attr:migstat    |                 None                 |
|     os-vol-mig-status-attr:name_id    |                 None                 |
|      os-vol-tenant-attr:tenant_id     |   588ce32358c4494088c070548f1f233a   |
|   os-volume-replication:driver_data   |                 None                 |
| os-volume-replication:extended_status |                 None                 |
|                  size                 |                  1                   |
|              snapshot_id              |                 None                 |
|              source_volid             |                 None                 |
|                 status                |              available               |
|              volume_type              |                 None                 |
+---------------------------------------+--------------------------------------+



インスタンスにアタッチする

作成したボリュームの仮想インスタンスに接続(アタッチ)してみましょう。
次のコマンドを実行します。

$ nova volume-attach <Server Name> <Volume ID>

ubuntu@matsuos-cluster-1:~/openstack$ nova volume-attach matsuos-cluster-1 924e30b2-4379-4594-a403-1463055c6ecf
+----------+--------------------------------------+
| Property | Value                                |
+----------+--------------------------------------+
| device   | /dev/vdb                             |
| id       | 924e30b2-4379-4594-a403-1463055c6ecf |
| serverId | 3ff875cc-3415-495a-ac4c-e4c264a2f159 |
| volumeId | 924e30b2-4379-4594-a403-1463055c6ecf |
+----------+--------------------------------------+


/dev/vdbという名前のデバイスが作成されて仮想インスタンスから見えるようになりました。

ファイルシステムを作成する

アタッチしたボリュームにファイルシステムを作成します。
次のコマンドを実行します。

$  sudo mkfs -t ext4 /dev/vdb

ubuntu@matsuos-cluster-1:~/openstack$ sudo mkfs -t ext4 /dev/vdb
mke2fs 1.42.9 (4-Feb-2014)
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
65536 inodes, 262144 blocks
13107 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=268435456
8 block groups
32768 blocks per group, 32768 fragments per group
8192 inodes per group
Superblock backups stored on blocks:
        32768, 98304, 163840, 229376

Allocating group tables: done
Writing inode tables: done
Creating journal (8192 blocks): done
Writing superblocks and filesystem accounting information: done


マウントする

最後にマウントして、OSから利用できるようにします。

$ sudo mount /dev/vdb /home/ubuntu/data_store -t ext4

ubuntu@matsuos-cluster-1:~/openstack$ sudo mount /dev/vdb /home/ubuntu/data_store -t ext4


dfコマンドを実行して、マウントされたファイルシステムを確認してみましょう。

$ df

ubuntu@matsuos-cluster-1:~/openstack$ df
Filesystem                         1K-blocks    Used Available Use% Mounted on
udev                                 1020076      12   1020064   1% /dev
tmpfs                                 205008     352    204656   1% /run
/dev/disk/by-label/cloudimg-rootfs  20608636 1003696  18735340   6% /
none                                       4       0         4   0% /sys/fs/cgroup
none                                    5120       0      5120   0% /run/lock
none                                 1025020       0   1025020   0% /run/shm
none                                  102400       0    102400   0% /run/user
/dev/vdb                              999320    1284    929224   1% /home/ubuntu/data_store


最後の行に/dev/vdbが/home/ubuntu/data_storeにマウントされていることがわかります。
























2016年3月21日月曜日

OpenStack Python APIでCinderボリュームを作成する

OpenStackのPython APIを使ってCinderボリュームを作成し、Ubuntuインスタンスにマウントしてみましょう。
まずはUbuntu 14.0インスタンスを用意します。

Python APIを導入する

次のコマンドを実行してPython APIを導入します。

sudo apt-get install python-openstackclient


環境変数をセットする


OpenStackのHorizonダッシュボードからopenrc.shをダウンロードします。
Compute→Access & Security→API Accessを開き、Download OpenStack RC Fileボタンを押します。

Ubuntu上にopenrc.shというファイルを作成し、中身をコピー&ペーストします。
こんな感じです。

#!/bin/bash

# To use an OpenStack cloud you need to authenticate against the Identity
# service named keystone, which returns a **Token** and **Service Catalog**.
# The catalog contains the endpoints for all services the user/tenant has
# access to - such as Compute, Image Service, Identity, Object Storage, Block
# Storage, and Networking (code-named nova, glance, keystone, swift,
# cinder, and neutron).
#
# *NOTE*: Using the 2.0 *Identity API* does not necessarily mean any other
# OpenStack API is version 2.0. For example, your cloud provider may implement
# Image API v1.1, Block Storage API v2, and Compute API v2.0. OS_AUTH_URL is
# only for the Identity API served through keystone.
export OS_AUTH_URL=https://xxxxxxxxxxxx.com:5000/v2.0

# With the addition of Keystone we have standardized on the term **tenant**
# as the entity that owns the resources.
export OS_TENANT_ID=588ce32358c4494088c070548f1f233a
export OS_TENANT_NAME="sandbox"
export OS_PROJECT_NAME="sandbox"

# In addition to the owning entity (tenant), OpenStack stores the entity
# performing the action as the **user**.
export OS_USERNAME="matsuos"

# With Keystone you pass the keystone password.
echo "Please enter your OpenStack Password: "
read -sr OS_PASSWORD_INPUT
export OS_PASSWORD=$OS_PASSWORD_INPUT

# If your configuration has multiple regions, we set that information here.
# OS_REGION_NAME is optional and only valid in certain environments.
export OS_REGION_NAME="RegionOne"
# Don't leave a blank variable, unset it if it was empty
if [ -z "$OS_REGION_NAME" ]; then unset OS_REGION_NAME; fi

実行権限を付与します。
chmod +x openrc.sh


openrc.shを実行してOpenStack APIの認証情報をセットします。
. ./openrc.sh

パスワードを入力します。

次のコマンドを入力して実行できる状態かどうか確認します。

nova list
+--------------------------------------+-----------------------+--------+------------+-------------+---------------------------------------+
| ID                                   | Name                  | Status | Task State | Power State | Networks                              |
+--------------------------------------+-----------------------+--------+------------+-------------+---------------------------------------+
| e7bc2bd0-37db-409e-84bc-b2adf868d14d | NW_test1              | ACTIVE | -          | Running     | internal=10.220.0.232, 161.202.195.75 |
こんな具合にインスタンス一覧が表示されれば準備完了です。

どんなAPIがあるか

次のコマンドを実行して、APIドキュメントを参照します。
pydoc cinderclient.v2.volumes


---------------------------------------------------------------------------------
Help on module cinderclient.v2.volumes in cinderclient.v2:

NAME
    cinderclient.v2.volumes - Volume interface (v2 extension).

FILE
    /usr/lib/python2.7/dist-packages/cinderclient/v2/volumes.py

CLASSES
    cinderclient.base.ManagerWithFind(abc.NewBase)
        VolumeManager
    cinderclient.base.Resource(__builtin__.object)
        Volume

    class Volume(cinderclient.base.Resource)
     |  A volume is an extra block level storage to the OpenStack instances.
     |
     |  Method resolution order:
     |      Volume
     |      cinderclient.base.Resource
     |      __builtin__.object
     |
     |  Methods defined here:
     |
     |  __repr__(self)
     |
     |  attach(self, instance_uuid, mountpoint, mode='rw')
     |      Set attachment metadata.
     |
     |      :param instance_uuid: uuid of the attaching instance.
     |      :param mountpoint: mountpoint on the attaching instance.
     |      :param mode: the access mode.
     |
     |  begin_detaching(self, volume)
     |      Begin detaching volume.
     |
     |  delete(self)
     |      Delete this volume.
     |
     |  detach(self)
     |      Clear attachment metadata.
     |
     |  extend(self, volume, new_size)
     |      Extend the size of the specified volume.
     |      :param volume: The UUID of the volume to extend
     |      :param new_size: The desired size to extend volume to.
     |
     |  force_delete(self)
     |      Delete the specified volume ignoring its current state.
     |
     |      :param volume: The UUID of the volume to force-delete.
     |
     |  initialize_connection(self, volume, connector)
     |      Initialize a volume connection.
     |
     |      :param connector: connector dict from nova.
     |
     |  migrate_volume(self, host, force_host_copy)
     |      Migrate the volume to a new host.
     |
     |  reserve(self, volume)
     |      Reserve this volume.
     |
     |  reset_state(self, state)
     |      Update the volume with the provided state.
     |
     |  retype(self, volume_type, policy)
     |      Change a volume's type.
     |
     |  roll_detaching(self, volume)
     |      Roll detaching volume.
     |
     |  set_metadata(self, volume, metadata)
     |      Set or Append metadata to a volume.
     |
     |      :param volume : The :class: `Volume` to set metadata on
     |      :param metadata: A dict of key/value pairs to set
     |
     |  terminate_connection(self, volume, connector)
     |      Terminate a volume connection.
     |
     |      :param connector: connector dict from nova.
     |
     |  unreserve(self, volume)
     |      Unreserve this volume.
     |
     |  update(self, **kwargs)
     |      Update the name or description for this volume.
     |
     |  update_all_metadata(self, metadata)
     |      Update all metadata of this volume.
     |
     |  update_readonly_flag(self, volume, read_only)
     |      Update the read-only access mode flag of the specified volume.
     |
     |      :param volume: The UUID of the volume to update.
     |      :param read_only: The value to indicate whether to update volume to
     |          read-only access mode.
     |
     |  upload_to_image(self, force, image_name, container_format, disk_format)
     |      Upload a volume to image service as an image.
     |
     |  ----------------------------------------------------------------------
     |  Methods inherited from cinderclient.base.Resource:
     |
     |  __eq__(self, other)
     |
     |  __getattr__(self, k)
     |
     |  __init__(self, manager, info, loaded=False)
     |
     |  get(self)
     |
     |  is_loaded(self)
     |
     |  set_loaded(self, val)
     |
     |  ----------------------------------------------------------------------
     |  Data descriptors inherited from cinderclient.base.Resource:
     |
     |  __dict__
     |      dictionary for instance variables (if defined)
     |
     |  __weakref__
     |      list of weak references to the object (if defined)
     |
     |  human_id
     |      Subclasses may override this provide a pretty ID which can be used
     |      for bash completion.
     |
     |  ----------------------------------------------------------------------
     |  Data and other attributes inherited from cinderclient.base.Resource:
     |
     |  HUMAN_ID = False

    class VolumeManager(cinderclient.base.ManagerWithFind)
     |  Manage :class:`Volume` resources.
     |
     |  Method resolution order:
     |      VolumeManager
     |      cinderclient.base.ManagerWithFind
     |      abc.NewBase
     |      cinderclient.base.Manager
     |      cinderclient.utils.HookableMixin
     |      __builtin__.object
     |
     |  Methods defined here:
     |
     |  attach(self, volume, instance_uuid, mountpoint, mode='rw')
     |      Set attachment metadata.
     |
     |      :param volume: The :class:`Volume` (or its ID)
     |                     you would like to attach.
     |      :param instance_uuid: uuid of the attaching instance.
     |      :param mountpoint: mountpoint on the attaching instance.
     |      :param mode: the access mode.
     |
     |  begin_detaching(self, volume)
     |      Begin detaching this volume.
     |
     |      :param volume: The :class:`Volume` (or its ID)
     |                     you would like to detach.
     |
     |  create(self, size, snapshot_id=None, source_volid=None, name=None, description=None, volume_type=None, user_id=None, project_id=None, availability_zone=None, metadata=None, imageRef=None, scheduler_hints=None)
     |      Create a volume.
     |
     |      :param size: Size of volume in GB
     |      :param snapshot_id: ID of the snapshot
     |      :param name: Name of the volume
     |      :param description: Description of the volume
     |      :param volume_type: Type of volume
     |      :param user_id: User id derived from context
     |      :param project_id: Project id derived from context
     |      :param availability_zone: Availability Zone to use
     |      :param metadata: Optional metadata to set on volume creation
     |      :param imageRef: reference to an image stored in glance
     |      :param source_volid: ID of source volume to clone from
     |      :param scheduler_hints: (optional extension) arbitrary key-value pairs
     |                          specified by the client to help boot an instance
     |      :rtype: :class:`Volume`
     |
     |  delete(self, volume)
     |      Delete a volume.
     |
     |      :param volume: The :class:`Volume` to delete.
     |
     |  delete_metadata(self, volume, keys)
     |      Delete specified keys from volumes metadata.
     |
     |      :param volume: The :class:`Volume`.
     |      :param keys: A list of keys to be removed.
     |
     |  detach(self, volume)
     |      Clear attachment metadata.
     |
     |      :param volume: The :class:`Volume` (or its ID)
     |                     you would like to detach.
     |
     |  extend(self, volume, new_size)
     |
     |  force_delete(self, volume)
     |
     |  get(self, volume_id)
     |      Get a volume.
     |
     |      :param volume_id: The ID of the volume to delete.
     |      :rtype: :class:`Volume`
     |
     |  get_encryption_metadata(self, volume_id)
     |      Retrieve the encryption metadata from the desired volume.
     |
     |      :param volume_id: the id of the volume to query
     |      :return: a dictionary of volume encryption metadata
     |
     |  initialize_connection(self, volume, connector)
     |      Initialize a volume connection.
     |
     |      :param volume: The :class:`Volume` (or its ID).
     |      :param connector: connector dict from nova.
     |
     |  list(self, detailed=True, search_opts=None)
     |      Get a list of all volumes.
     |
     |      :rtype: list of :class:`Volume`
     |
     |  migrate_volume(self, volume, host, force_host_copy)
     |      Migrate volume to new host.
     |
     |      :param volume: The :class:`Volume` to migrate
     |      :param host: The destination host
     |      :param force_host_copy: Skip driver optimizations
     |
     |  migrate_volume_completion(self, old_volume, new_volume, error)
     |      Complete the migration from the old volume to the temp new one.
     |
     |      :param old_volume: The original :class:`Volume` in the migration
     |      :param new_volume: The new temporary :class:`Volume` in the migration
     |      :param error: Inform of an error to cause migration cleanup
     |
     |  reserve(self, volume)
     |      Reserve this volume.
     |
     |      :param volume: The :class:`Volume` (or its ID)
     |                     you would like to reserve.
     |
     |  reset_state(self, volume, state)
     |      Update the provided volume with the provided state.
     |
     |  retype(self, volume, volume_type, policy)
     |      Change a volume's type.
     |
     |      :param volume: The :class:`Volume` to retype
     |      :param volume_type: New volume type
     |      :param policy: Policy for migration during the retype
     |
     |  roll_detaching(self, volume)
     |      Roll detaching this volume.
     |
     |      :param volume: The :class:`Volume` (or its ID)
     |                     you would like to roll detaching.
     |
     |  set_metadata(self, volume, metadata)
     |      Update/Set a volumes metadata.
     |
     |      :param volume: The :class:`Volume`.
     |      :param metadata: A list of keys to be set.
     |
     |  terminate_connection(self, volume, connector)
     |      Terminate a volume connection.
     |
     |      :param volume: The :class:`Volume` (or its ID).
     |      :param connector: connector dict from nova.
     |
     |  unreserve(self, volume)
     |      Unreserve this volume.
     |
     |      :param volume: The :class:`Volume` (or its ID)
     |                     you would like to unreserve.
     |
     |  update(self, volume, **kwargs)
     |      Update the name or description for a volume.
     |
     |      :param volume: The :class:`Volume` to delete.
     |
     |  update_all_metadata(self, volume, metadata)
     |      Update all metadata of a volume.
     |
     |      :param volume: The :class:`Volume`.
     |      :param metadata: A list of keys to be updated.
     |
     |  update_readonly_flag(self, volume, flag)
     |
     |  upload_to_image(self, volume, force, image_name, container_format, disk_format)
     |      Upload volume to image service as image.
     |
     |      :param volume: The :class:`Volume` to upload.
     |
     |  ----------------------------------------------------------------------
     |  Data and other attributes defined here:
     |
     |  __abstractmethods__ = frozenset([])
     |
     |  resource_class = <class 'cinderclient.v2.volumes.Volume'>
     |      A volume is an extra block level storage to the OpenStack instances.
     |
     |  ----------------------------------------------------------------------
     |  Methods inherited from cinderclient.base.ManagerWithFind:
     |
     |  find(self, **kwargs)
     |      Find a single item with attributes matching ``**kwargs``.
     |
     |      This isn't very efficient: it loads the entire list then filters on
     |      the Python side.
     |
     |  findall(self, **kwargs)
     |      Find all items with attributes matching ``**kwargs``.
     |
     |      This isn't very efficient: it loads the entire list then filters on
     |      the Python side.
     |
     |  ----------------------------------------------------------------------
     |  Methods inherited from cinderclient.base.Manager:
     |
     |  __init__(self, api)
     |
     |  completion_cache(*args, **kwds)
     |      The completion cache store items that can be used for bash
     |      autocompletion, like UUIDs or human-friendly IDs.
     |
     |      A resource listing will clear and repopulate the cache.
     |
     |      A resource create will append to the cache.
     |
     |      Delete is not handled because listings are assumed to be performed
     |      often enough to keep the cache reasonably up-to-date.
     |
     |  write_to_completion_cache(self, cache_type, val)
     |
     |  ----------------------------------------------------------------------
     |  Class methods inherited from cinderclient.utils.HookableMixin:
     |
     |  add_hook(cls, hook_type, hook_func) from abc.ABCMeta
     |
     |  run_hooks(cls, hook_type, *args, **kwargs) from abc.ABCMeta
     |
     |  ----------------------------------------------------------------------
     |  Data descriptors inherited from cinderclient.utils.HookableMixin:
     |
     |  __dict__
     |      dictionary for instance variables (if defined)
     |
     |  __weakref__
     |      list of weak references to the object (if defined)




---------------------------------------------------------------------------------

Pythonコードを書く

create_volume.pyというファイル名のPythonコードを書いてみましょう。
このコードサンプルはmatsuos-vol1という名前で1GBのCinderボリュームを1個作成します。
すでに同じ名前のボリュームがある場合は作成しません。



from os import environ as env

volume_name='matsuos-vol1'
volume_size=1

import cinderclient.client
cinder = cinderclient.client.Client("2",auth_url=env['OS_AUTH_URL'],
                                username=env['OS_USERNAME'],
                                api_key=env['OS_PASSWORD'],
                                project_id=env['OS_TENANT_NAME'])


volumes = cinder.volumes.findall(name = volume_name)
if len(volumes) > 0 :
  print volume_name +' is already exist'
  exit()
else :
  volume = cinder.volumes.create(volume_size, name=volume_name)
  print volume_name +' has been created'


Pythonコードを実行する


次のコマンドを実行します。
python create_volume.py

初めて実行すると次のようなメッセージが返ります。
matsuos-vol1 has been created

2回目以降に実行すると次のようなメッセージが返ります。
matsuos-vol1 is already exist

作成したボリュームは次のコマンドで削除できます。

cinder list
で一覧を表示し、IDの部分をコピーして
cinder delete の後ろにペーストして実行すると削除されます。
IDは複数指定可能です。


いかがですか?
Python APIを使ってCinderボリュームを作成するのは簡単ですね。











2016年3月20日日曜日

Serf を使ってOpenStack上にクラスタ構成を組む

OpenStackデファクトのHypervisorであるKVMにはVMware HAのようなクラスタ機能は含まれていませんので、ユーザー側でクラスタソフトウェアを用いてクラスタ構成を構築する必要があります。
The cluster function like VMware HA is not included in KVM, which is a Hypervisor of the OpenStack de facto, so it is necessary for the user side to construct a cluster configuration using cluster software.

Hashicorp社が開発するオープンソースのSerfを活用して、OpenStack上の複数インスタンスでクラスタ構成を作成してみたいと思います。
I would like to take advantage of open source Serf developed by Hashicorp to create a cluster configuration with multiple instances on OpenStack.

Serfは以下のURLから入手できます。ドキュメントも整備されていますのでこちらを参考に作業を進めます。
Serf can be obtained from the following URL. Since the document is well maintained, we will proceed with reference here.

https://www.serfdom.io/

GETTING STARTEDに沿って進めます。
Proceed along GETTING STARTED.

https://www.serfdom.io/intro/getting-started/install.html

Serfをダウンロードする - Download Serf

https://www.serfdom.io/downloads.html

保存先のURLをコピーして次のようなコマンドでダウンロードします。
Copy the URL of the save destination and download with the command like the following.

curl -L -O https://releases.hashicorp.com/serf/0.7.0/serf_0.7.0_linux_amd64.zip

zipファイルを解凍します。

Extract the zip file.

unzip serf_0.7.0_linux_amd64.zip

Serfを導入する - Install Serf

SerfはダウンロードしてZIPを回答すると直接実行できるバイナリになります。
これを/usr/local/binにコピーします。
Serf is a binary that can be directly executed by downloading and answering ZIP. Copy this to / usr / local / bin.

sudo cp serf /usr/local/bin

動作するかどうか試します。
I will try to work.


$ serf
usage: serf [--version] [--help] <command> [<args>]

Available commands are:
    agent           Runs a Serf agent
    event           Send a custom event through the Serf cluster
    force-leave     Forces a member of the cluster to enter the "left" state
    info            Provides debugging information for operators
    join            Tell Serf agent to join cluster
    keygen          Generates a new encryption key
    keys            Manipulate the internal encryption keyring used by Serf
    leave           Gracefully leaves the Serf cluster and shuts down
    members         Lists the members of a Serf cluster
    monitor         Stream logs from a Serf agent
    query           Send a query to the Serf cluster
    reachability    Test network reachability
    rtt             Estimates network round trip time between nodes
    tags            Modify tags of a running Serf agent
    version         Prints the Serf version

このようにコマンドの利用方法が出力されれば導入は完了です。
Installation of this command will be completed if output method of command is output like this.

Serfイベントハンドラを作成する - Create event handler

Serfにイベントが発生した時に呼び出されるイベントハンドラを作成します。
Create an event handler called when an event occurs in Serf.

#!/bin/bash

echo
echo "New event: ${SERF_EVENT}. Data follows..."
while read line; do
    printf "${line}\n"
done

これを適当なディレクトリに置きます。
筆者の場合は/home/ubuntu/serfの下に置きました。
Put this in the appropriate directory.
In my case I put it under / home / ubuntu / serf.

実行できるようchmodで実行権限を付与します。
Grant execute privilege on chmod so that it can be executed.

chmod +x handler.sh

Serf起動スクリプトを作成する - Create startup script

/home/ubuntu/serfディレクトリの下にstart-serf.shという名前でserfの起動スクリプトを作ります。
Under the / home / ubuntu / serf directory create a serf startup script named start-serf.sh.

nohup serf agent -node=matsuos-cluster-1 \
        -bind=10.220.0.248 \
        -event-handler=/home/ubuntu/serf/handler.sh \
        -log-level=debug &

nodeに指定しているのはSerf上で見える自分自身の名前です。
bindに指定しているのはSerfが使用するNICのIPアドレスです。
何も指定しないとloopbackアドレスが指定され、外部と通信できません。
event-handerには先ほど作成したイベントハンドラのパスを指定します。
log-levelは指定しなくても構いませんが、debugモードにすると詳細のログを閲覧できます。
nohupコマンドと&(アンパサンド)を使用して、セッションが切れてもバックグラウンドで作動するようにします。
The node you specify is yourself name visible on Serf.
The one specified for bind is the IP address of the NIC used by Serf.
If nothing is specified, the loopback address is specified and communication with the outside can not be performed.
For event-hander, specify the path of the event handler created earlier.
You do not have to specify log-level, but you can view detailed logs in debug mode.
Use the nohup command and & (ampersand) so that it runs in the background even if the session expires.


実行できるようchmodで実行権限を付与します。
Grant execute privilege on chmod so that it can be executed.

chmod +x start-serf.sh

Serfを起動する - Start Serf

/home/ubuntu/serfに移り、start-serf.shを実行します。
Go to / home / ubuntu / serf and run start-serf.sh.

./start-serf.sh

2号機にも同様の設定を行う - Make the same setting for unit No.2

クラスタの対向となる2号機にも同様の設定を行います。
Make the same settings for Unit 2, which is the opposite of the cluster.
  • Serfのダウンロード (Download Serf)
  • Serfの導入 (Install Serf)
  • Serfイベントハンドラの作成 (Create event handler)
  • Serf起動スクリプトの作成 (Create startup script)
  • Serfを起動する (Start Serf)

Joinする

1号機と2号機でそれぞれSerfエージェントがバックグラウンドで稼働しています。
この状態ではお互いは何も通信していないので、お互いを認識していません。
認識させるためには、どちらかから相手側にJoinする必要があります。
The Serf agent runs in the background in Unit 1 and Unit 2, respectively.
In this state, they do not communicate with each other, so they do not recognize each other.
In order to recognize, you need to join from the other side to the other side.

ここでは、2号機から以下のコマンドを実行し1号機のSerfにJoinします。
(1号機から2号機へJoinしてもOKです)
In this case, execute the following command from Unit 2 and join Serf of Unit 1.
(It is OK even if Join from Unit 1 to Unit 2)

serf join 10.220.0.248
Successfully joined cluster by contacting 1 nodes.

次のコマンドを実行しメンバに登録されたことを確認します。
Execute the following command to confirm that it is registered in the member.

serf members
matsuos-cluster-2  10.220.0.249:7946  alive
matsuos-cluster-1  10.220.0.248:7946  alive

Logを確認する - Confirm log

イベントハンドラが正しく作動したかどうか、ログを確認します。
起動スクリプトでnohupコマンドを利用したので、標準出力はnohup.outファイルに書きだされています。
こちらは1号機のnohup.outの内容です。
Check the log whether the event handler worked properly.
I used the nohup command in the startup script, so the standard output is written in the nohup.out file.
This is the contents of No. 1 nohup.out.

==> Starting Serf agent...
==> Starting Serf agent RPC...
==> Serf agent running!
         Node name: 'matsuos-cluster-1'
         Bind addr: '10.220.0.248:7946'
          RPC addr: '127.0.0.1:7373'
         Encrypted: false
          Snapshot: false
           Profile: lan

==> Log data will now stream in as it occurs:

    2016/03/20 11:15:04 [INFO] agent: Serf agent starting
    2016/03/20 11:15:04 [INFO] serf: EventMemberJoin: matsuos-cluster-1 10.220.0.248
    2016/03/20 11:15:05 [INFO] agent: Received event: member-join
    2016/03/20 11:15:05 [DEBUG] agent: Event 'member-join' script output:
New event: member-join. Data follows...
matsuos-cluster-1      10.220.0.248

(ここで1号機自身が起動したことによるmember-joinイベントが発行されました)
    2016/03/20 11:15:48 [DEBUG] memberlist: TCP connection from=10.220.0.249:60034
    2016/03/20 11:15:48 [INFO] serf: EventMemberJoin: matsuos-cluster-2 10.220.0.249
    2016/03/20 11:15:48 [DEBUG] serf: messageJoinType: matsuos-cluster-2
    2016/03/20 11:15:49 [DEBUG] serf: messageJoinType: matsuos-cluster-2
    2016/03/20 11:15:49 [DEBUG] serf: messageJoinType: matsuos-cluster-2
    2016/03/20 11:15:49 [DEBUG] serf: messageJoinType: matsuos-cluster-2
    2016/03/20 11:15:49 [INFO] agent: Received event: member-join
    2016/03/20 11:15:49 [DEBUG] agent: Event 'member-join' script output:
New event: member-join. Data follows...
matsuos-cluster-2      10.220.0.249
(ここで2号機からjoinコマンドによってmember-joinイベントが発行されました)
    2016/03/20 11:15:50 [DEBUG] memberlist: Initiating push/pull sync with: 10.220.0.249:7946
    2016/03/20 11:16:20 [DEBUG] memberlist: Initiating push/pull sync with: 10.220.0.249:7946
    2016/03/20 11:16:35 [DEBUG] memberlist: TCP connection from=10.220.0.249:60036
    2016/03/20 11:16:50 [DEBUG] memberlist: Initiating push/pull sync with: 10.220.0.249:7946
    2016/03/20 11:17:05 [DEBUG] memberlist: TCP connection from=10.220.0.249:60037
    2016/03/20 11:17:20 [DEBUG] memberlist: Initiating push/pull sync with: 10.220.0.249:7946
    2016/03/20 11:17:35 [DEBUG] memberlist: TCP connection from=10.220.0.249:60038
    2016/03/20 11:17:50 [DEBUG] memberlist: Initiating push/pull sync with: 10.220.0.249:7946

他のイベントを送ってみる - Send another events

2号機のSerfエージェントをKillしてみます。
I will try to kill the Serf agent of Unit 2.

ubuntu@matsuos-cluster-2:~/serf$ ps
  PID TTY          TIME CMD
 1368 pts/0    00:00:00 bash
 1604 pts/0    00:00:01 serf
 1629 pts/0    00:00:00 ps
ubuntu@matsuos-cluster-2:~/serf$ kill 1604
ubuntu@matsuos-cluster-2:~/serf$ ps
  PID TTY          TIME CMD
 1368 pts/0    00:00:00 bash
 1630 pts/0    00:00:00 ps

1号機のnohup.outファイルを見てみましょう。
Let's look at the nohup.out file of Unit 1.

    2016/03/20 11:23:20 [DEBUG] memberlist: Initiating push/pull sync with: 10.220.0.249:7946
    2016/03/20 11:23:35 [DEBUG] memberlist: TCP connection from=10.220.0.249:60050
    2016/03/20 11:23:50 [DEBUG] memberlist: Initiating push/pull sync with: 10.220.0.249:7946
    2016/03/20 11:24:05 [DEBUG] memberlist: TCP connection from=10.220.0.249:60051
    2016/03/20 11:24:20 [DEBUG] memberlist: Initiating push/pull sync with: 10.220.0.249:7946
    2016/03/20 11:24:34 [DEBUG] memberlist: Failed UDP ping: matsuos-cluster-2 (timeout reached)
(ここで2号機へのhart beatが失敗していることがわかります)    2016/03/20 11:24:35 [INFO] memberlist: Suspect matsuos-cluster-2 has failed, no acks received
    2016/03/20 11:24:36 [DEBUG] memberlist: Failed UDP ping: matsuos-cluster-2 (timeout reached)
    2016/03/20 11:24:37 [INFO] memberlist: Suspect matsuos-cluster-2 has failed, no acks received
    2016/03/20 11:24:38 [DEBUG] memberlist: Failed UDP ping: matsuos-cluster-2 (timeout reached)
    2016/03/20 11:24:39 [INFO] memberlist: Suspect matsuos-cluster-2 has failed, no acks received
    2016/03/20 11:24:39 [DEBUG] memberlist: Failed UDP ping: matsuos-cluster-2 (timeout reached)
    2016/03/20 11:24:40 [INFO] memberlist: Suspect matsuos-cluster-2 has failed, no acks received
    2016/03/20 11:24:40 [INFO] memberlist: Marking matsuos-cluster-2 as failed, suspect timeout reached
    2016/03/20 11:24:40 [INFO] serf: EventMemberFailed: matsuos-cluster-2 10.220.0.249
    2016/03/20 11:24:41 [INFO] agent: Received event: member-failed
    2016/03/20 11:24:41 [DEBUG] agent: Event 'member-failed' script output:
New event: member-failed. Data follows...
matsuos-cluster-2      10.220.0.249
(ここでmember-failedイベントが発行されました)    2016/03/20 11:25:04 [INFO] serf: attempting reconnect to matsuos-cluster-2 10.220.0.249:7946
    2016/03/20 11:25:34 [INFO] serf: attempting reconnect to matsuos-cluster-2 10.220.0.249:7946


イベントハンドラにリカバリ処理を記述することによって、クラスタ構成を取ることができることがご理解いただけましたでしょうか?
Did you understand that you can take cluster configuration by describing recovery processing in event handler?

例えば、Cinderボリュームを付け替えて、サービスを再起動する、といったリカバリ処理を記述することになると思います。
For example, I think that you will write a recovery process such as changing the Cinder volume and restarting the service.



2016年3月9日水曜日

OpenStack用Windows Server 2012イメージを作成する(1)

ステップ1では、中継用Ubuntu KVM環境を用意します。以下の手順に従ってKVMが作動し、リモートPCからGUIにアクセスできるようにしましょう。

ステップ2でISOイメージからOpenStack用イメージを作成していきます。

インスタンスを用意する

OpenStack環境上にUbuntu 14のインスタンスを作成します。
KVMも入れるのでメモリサイズが大きいフレーバを使用します。
筆者はm1.largeで作成しました。4コア、8GBメモリです。

インスタンスを作成したらとりあえずパッケージを最新化します。
  • sudo apt-get update

GUIをインストールする


デスクトップマネージャのxfceをインストールします。

  • sudo apt-get install xfce4
  • echo xfce4-session >~/.xsession
Ubuntu-desktopだとxrdpをサポートしないのでxfceが良いです。

リモートデスクトップサーバをインストールする


xrdpをインストールします。
  • sudo apt-get install xrdp


日本語キーボード対応設定を行う

 xfce4デスクトップは標準だとUSキーボード配列になっているので、日本語キーボードに変更します。

  • cd /tmp
  • wget http://w.vmeta.jp/temp/km-0411.ini
  • cd /etc/xrdp
  • sudo cp /tmp/km-0411.ini .
  • sudo ln -s km-0411.ini km-e0200411.ini
  • sudo ln -s km-0411.ini km-e0010411.ini
  • sudo /etc/init.d/xrdp restart

リブート

  • reboot

接続する


筆者の環境はWindows8.1なのでストアから「リモートデスクトップ」をダウンロードして実行しました。

インスタンスに付与したFloating IPを指定して接続ボタンを押すと、しばらくしてGUIのログイン画面が表示されます。

KVMを導入する

Linuxのハイパーバイザの一つ、KVMを導入します。
この記事を参考にしました。
http://symfoware.blog68.fc2.com/blog-entry-1398.html

モジュールを導入するだけで利用可能になります。
  • sudo apt-get install kvm virt-manager libvirt-bin bridge-utils
次のコマンドを実行するとKVMの管理コンソールが起動します。
  • virt-manager


X-Windowサーバを使う方法もあります

リモートデスクトップを使う方法を解説しましたが、この方式はUbuntuのGUIが全画面でアクセスできて、Ubuntuのデスクトップ環境をそっくり再現するには良い方法だと思います。

ただ、RDP用にSecurity Groupを設定したり、Firewallに穴を開ける必要があり、セキュリティ的にどうかと思われる方もいらっしゃるかもしれません。

そこで代替策として、X-WIndowサーバとSSHによるX11転送という方法を解説します。

こちらの記事を参考にしました。
http://www.ep.sci.hokudai.ac.jp/~epnetfan/tebiki/server-login/xming.html

XmingというフリーのWindows向けX-Windowサーバです。
Xming-fontも導入しましょう。

SSHソフトはバンドルされていますが、導入しないオプションも提供されています。
その場合は外部のSSHソフト(Puttyなど)を指定します。

筆者もPuttyを使っているので、記事と同じ手順でうまくいきました。
PuttyのSetting画面でX11転送機能を有効にして接続しログインします。
そのコンソールから実行したGUI対応コマンドはすべてWindows上にウィンドウが現れます。
試しにxeyesとか、xfce4-terminalとか、実行してみてください。

※xfce4-terminalはUbuntu上に別途インストールする必要があります。
  • sudo apt-get install xfce4-terminal