2017年8月22日火曜日

packer を使って vmx ファイルを作成し vSphere 上に VM を作成してみた

概要

前回 packer を使って AWS 上に AMI を作成してみました
今回は VMware Builder という vSphere 上に VM を作成できる機能を試してみました

環境

  • ESXi 6.5.0
  • Ubuntu 16.04
  • packer 1.0.4

ESXi 準備

今回ビルドは ESXi 上で行います
ローカルでもできるのですが、VMware workstation なるものをインストールしなければいけないらしく自分にとってはハードルが高かったのでビルド用の ESXi を用意しました
ESXi の IP は 192.168.100.6 とします

SSH の有効化

ESXi に SSH できるようにしてください

GuestIPHack の有効化

GuestIPHack という設定を有効にします
これもコマンドから行います

  • esxcli system settings advanced set -o /Net/GuestIPHack -i 1
  • esxcli system settings advanced list | grep -A 9 ‘/Net/GuestIPHack’

で有効になっていれば OK です
再起動は不要です

VNC ポートをオープンする

ESXi に VNC のポート (5900 番) でアクセスできる必要があります
ESXi のファイアウォールを管理するファイルを直接編集してオープンします

  • chmod 644 /etc/vmware/firewall/service.xml
  • chmod +t /etc/vmware/firewall/service.xml
  • vi /etc/vmware/firewall/service.xml
<service id="1000">
  <id>packer-vnc</id>
  <rule id="0000">
    <direction>inbound</direction>
    <protocol>tcp</protocol>
    <porttype>dst</porttype>
    <port>
      <begin>5900</begin>
      <end>6000</end>
    </port>
  </rule>
  <enabled>true</enabled>
  <required>true</required>
</service>
  • chmod 444 /etc/vmware/firewall/service.xml
  • esxcli network firewall refresh

で OK です

ストレージの準備

ストレージを用意しましょう
デフォルトで付いているローカルストレージでも OK です
この領域に iso ファイルをアップロードします
今回は ESXi に datastore1 というストレージをマウントしました

packer インストール

構築した ESXi にアクセスできるサーバを準備します
今回は Ubuntu 16.04 を準備しました
そのサーバに packer をインストールします
インストール方法は以下を参考にしてください

リソースファイルの取得

今回は既存のリソースファイルを拝借します

これに含まれている preseed と scripts を使いまわします

テンプレート json の準備

実行するテンプレート json を準備します
今回は Ubuntu をビルドします

まずは変数を管理するファイルを作成します

  • cd packer-esxi
  • vim variables.json
{
  "esxi_host": "192.168.100.6",
  "esxi_datastore": "datastore1/primary",
  "esxi_username": "root",
  "esxi_password": "root_password"
}

特に説明は不要かなと思います
esxi_datastore は指定したパスにディレクトリを作成します
用意したストレージ上に作成されるように指定してください

次にテンプレート json です
長いです

  • cd packer-esxi
  • vim my-ubuntu-1604-base.json
{
  "builders": [{
    "name": "ubuntu-1604-base",
    "vm_name": "ubuntu-1604-base",
    "type": "vmware-iso",
    "guest_os_type": "ubuntu-64",
    "tools_upload_flavor": "linux",
    "headless": false,

    "iso_url": "http://releases.ubuntu.com/xenial/ubuntu-16.04.3-server-amd64.iso",
    "iso_checksum": "a06cd926f5855d4f21fb4bc9978a35312f815fbda0d0ef7fdc846861f4fc4600",
    "iso_checksum_type": "sha256",

    "ssh_username": "nullgrid",
    "ssh_password": "nullgrid",
    "ssh_timeout": "15m",

    "disk_type_id": "thin",

    "floppy_files": [
      "preseed/ubuntu.cfg"
    ],

    "boot_command": [
      "<enter><wait><f6><esc><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
      "<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
      "<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
      "<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
      "/install/vmlinuz noapic ",
      "preseed/file=/floppy/ubuntu.cfg ",
      "debian-installer=en_US auto locale=en_US kbd-chooser/method=us ",
      "hostname={{ .Name }} ",
      "fb=false debconf/frontend=noninteractive ",
      "keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ",
      "keyboard-configuration/variant=USA console-setup/ask_detect=false ",
      "grub-installer/bootdev=/dev/sda ",
      "initrd=/install/initrd.gz -- <enter>"
    ],

    "shutdown_command": "echo 'shutdown -P now' > shutdown.sh; echo 'nullgrid'|sudo -S sh 'shutdown.sh'",

    "remote_type": "esx5",
    "remote_host": "{{user `esxi_host`}}",
    "remote_datastore": "{{user `esxi_datastore`}}",
    "remote_username": "{{user `esxi_username`}}",
    "remote_password": "{{user `esxi_password`}}",
    "keep_registered": true,

    "vmx_data": {
      "ethernet0.networkName": "VM Network"
    },
    "vnc_port_min": "5900",
    "vnc_port_max": "5911",
    "vnc_bind_address": "0.0.0.0",
    "vnc_disable_password": "true"
  }],

  "provisioners": [
    {
      "type": "shell",
      "scripts": [
        "scripts/open-vm-tools.sh"
      ],

      "execute_command": "echo 'nullgrid' | {{ .Vars }} sudo -E -S bash '{{ .Path }}'"
    }
  ]
}

簡単にポイントを説明します
iso_url, iso_checksum でダウンロードする iso を指定します
フロッピーディスクを接続し preseed ディレクトリをマウントしています

そして一番のポイントは boot_command です
普通 iso から OS をインストールする場合 GUI でポチポチしていきますが packer の場合ここに記載したコマンドを自動で実行します
これを VNC を使って実行するため VNC のポートを開放しています
また作成した VM 上でも VNC が立ち上がっています
boot_command 内でフロッピーにマウントした preseed/ubuntu.cfg を元に細かい OS の初期設定を行っています

あとはビルドする ESXi の場所や VNC のポートを指定しています

packer 実行

  • cd packer-esxi
  • packer build -var-file variables.json my-ubuntu-1604-base.json

でビルドが始まります
結構ログが長いですが以下のようになれば成功です

ubuntu-1604-base output will be in this color.

==> ubuntu-1604-base: Downloading or copying ISO
    ubuntu-1604-base: Downloading or copying: http://releases.ubuntu.com/xenial/ubuntu-16.04.3-server-amd64.iso
==> ubuntu-1604-base: Creating floppy disk...
    ubuntu-1604-base: Copying files flatly from floppy_files
    ubuntu-1604-base: Copying file: preseed/ubuntu.cfg
    ubuntu-1604-base: Done copying files from floppy_files
    ubuntu-1604-base: Collecting paths from floppy_dirs
    ubuntu-1604-base: Resulting paths from floppy_dirs : []
    ubuntu-1604-base: Done copying paths from floppy_dirs
==> ubuntu-1604-base: Uploading Floppy to remote machine...
==> ubuntu-1604-base: Uploading ISO to remote machine...
==> ubuntu-1604-base: Creating virtual machine disk
==> ubuntu-1604-base: Building and writing VMX file
==> ubuntu-1604-base: Registering remote VM...
==> ubuntu-1604-base: Starting virtual machine...
==> ubuntu-1604-base: Waiting 10s for boot...
==> ubuntu-1604-base: Connecting to VM via VNC
==> ubuntu-1604-base: Typing the boot command over VNC...
==> ubuntu-1604-base: Waiting for SSH to become available...
==> ubuntu-1604-base: Connected to SSH!
==> ubuntu-1604-base: Provisioning with shell script: scripts/open-vm-tools.sh
    ubuntu-1604-base: Reading package lists...
    ubuntu-1604-base: Building dependency tree...
    ubuntu-1604-base: Reading state information...
    ubuntu-1604-base: The following additional packages will be installed:
    ubuntu-1604-base: ethtool libdumbnet1 libmspack0 zerofree
    ubuntu-1604-base: Suggested packages:
    ubuntu-1604-base: open-vm-tools-desktop
    ubuntu-1604-base: The following NEW packages will be installed:
    ubuntu-1604-base: ethtool libdumbnet1 libmspack0 open-vm-tools zerofree
    ubuntu-1604-base: 0 upgraded, 5 newly installed, 0 to remove and 6 not upgraded.
    ubuntu-1604-base: Need to get 601 kB of archives.
    ubuntu-1604-base: After this operation, 2,224 kB of additional disk space will be used.
    ubuntu-1604-base: Get:1 http://archive.ubuntu.com/ubuntu xenial/main amd64 ethtool amd64 1:4.5-1 [97.5 kB]
    ubuntu-1604-base: Get:2 http://archive.ubuntu.com/ubuntu xenial/main amd64 libdumbnet1 amd64 1.12-7 [25.7 kB]
    ubuntu-1604-base: Get:3 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 libmspack0 amd64 0.5-1ubuntu0.16.04.1 [37.0 kB]
    ubuntu-1604-base: Get:4 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 open-vm-tools amd64 2:10.0.7-3227872-5ubuntu1~16.04.1 [433 kB]
    ubuntu-1604-base: Get:5 http://archive.ubuntu.com/ubuntu xenial/main amd64 zerofree amd64 1.0.3-1 [7,928 B]
    ubuntu-1604-base: [sudo] password for nullgrid: debconf: unable to initialize frontend: Dialog
    ubuntu-1604-base: debconf: (Dialog frontend will not work on a dumb terminal, an emacs shell buffer, or without a controlling terminal.)
    ubuntu-1604-base: debconf: falling back to frontend: Readline
    ubuntu-1604-base: debconf: unable to initialize frontend: Readline
    ubuntu-1604-base: debconf: (This frontend requires a controlling tty.)
    ubuntu-1604-base: debconf: falling back to frontend: Teletype
    ubuntu-1604-base: dpkg-preconfigure: unable to re-open stdin:
    ubuntu-1604-base: Fetched 601 kB in 1s (348 kB/s)
    ubuntu-1604-base: Selecting previously unselected package ethtool.
    ubuntu-1604-base: (Reading database ... 59034 files and directories currently installed.)
    ubuntu-1604-base: Preparing to unpack .../ethtool_1%3a4.5-1_amd64.deb ...
    ubuntu-1604-base: Unpacking ethtool (1:4.5-1) ...
    ubuntu-1604-base: Selecting previously unselected package libdumbnet1:amd64.
    ubuntu-1604-base: Preparing to unpack .../libdumbnet1_1.12-7_amd64.deb ...
    ubuntu-1604-base: Unpacking libdumbnet1:amd64 (1.12-7) ...
    ubuntu-1604-base: Selecting previously unselected package libmspack0:amd64.
    ubuntu-1604-base: Preparing to unpack .../libmspack0_0.5-1ubuntu0.16.04.1_amd64.deb ...
    ubuntu-1604-base: Unpacking libmspack0:amd64 (0.5-1ubuntu0.16.04.1) ...
    ubuntu-1604-base: Selecting previously unselected package open-vm-tools.
    ubuntu-1604-base: Preparing to unpack .../open-vm-tools_2%3a10.0.7-3227872-5ubuntu1~16.04.1_amd64.deb ...
    ubuntu-1604-base: Unpacking open-vm-tools (2:10.0.7-3227872-5ubuntu1~16.04.1) ...
    ubuntu-1604-base: Selecting previously unselected package zerofree.
    ubuntu-1604-base: Preparing to unpack .../zerofree_1.0.3-1_amd64.deb ...
    ubuntu-1604-base: Unpacking zerofree (1.0.3-1) ...
    ubuntu-1604-base: Processing triggers for man-db (2.7.5-1) ...
    ubuntu-1604-base: Processing triggers for libc-bin (2.23-0ubuntu9) ...
    ubuntu-1604-base: Processing triggers for systemd (229-4ubuntu19) ...
    ubuntu-1604-base: Processing triggers for ureadahead (0.100.0-19) ...
    ubuntu-1604-base: Setting up ethtool (1:4.5-1) ...
    ubuntu-1604-base: Setting up libdumbnet1:amd64 (1.12-7) ...
    ubuntu-1604-base: Setting up libmspack0:amd64 (0.5-1ubuntu0.16.04.1) ...
    ubuntu-1604-base: Setting up open-vm-tools (2:10.0.7-3227872-5ubuntu1~16.04.1) ...
    ubuntu-1604-base: Setting up zerofree (1.0.3-1) ...
    ubuntu-1604-base: Processing triggers for libc-bin (2.23-0ubuntu9) ...
    ubuntu-1604-base: Processing triggers for systemd (229-4ubuntu19) ...
    ubuntu-1604-base: Processing triggers for ureadahead (0.100.0-19) ...
==> ubuntu-1604-base: Gracefully halting virtual machine...
    ubuntu-1604-base: Waiting for VMware to clean up after itself...
==> ubuntu-1604-base: Deleting unnecessary VMware files...
    ubuntu-1604-base: Deleting: /vmfs/volumes/datastore1/primary/output-ubuntu-1604-base/vmware.log
==> ubuntu-1604-base: Compacting the disk image
==> ubuntu-1604-base: Cleaning VMX prior to finishing up...
    ubuntu-1604-base: Unmounting floppy from VMX...
    ubuntu-1604-base: Detaching ISO from CD-ROM device...
    ubuntu-1604-base: Disabling VNC server...
==> ubuntu-1604-base: Keeping virtual machine registered with ESX host (keep_registered = true)
Build 'ubuntu-1604-base' finished.

==> Builds finished. The artifacts of successful builds are:
--> ubuntu-1604-base: VM files in directory: /vmfs/volumes/datastore1/primary/output-ubuntu-1604-base

成功すると ESXi 上に vmx ファイルと vmdk ファイルができています

[root@esxi:~] ls -ltr /vmfs/volumes/datastore1/primary/output-ubuntu-1604-base
total 2373640
-rw-r--r--    1 root     root             0 Aug 21 02:07 ubuntu-1604-base.vmsd
-rw-------    1 root     root           546 Aug 21 02:16 disk.vmdk
-rw-------    1 root     root          8684 Aug 21 02:16 ubuntu-1604-base.nvram
-rw-------    1 root     root   41943040000 Aug 21 02:16 disk-flat.vmdk
-rw-r--r--    1 root     root          2448 Aug 21 02:16 ubuntu-1604-base.vmx

インスタンスはすでに Discoverd virtual machine ディレクトリ配下にあります
試しに起動して中を確認してみましょう
192.168.100.7 は packer で作成した Ubuntu VM です

  • ssh nullgrid@192.168.100.7
nullgrid@unassigned-hostname:~$ dpkg -l | grep open-vm-tools
ii  open-vm-tools                      2:10.0.7-3227872-5ubuntu1~16.04.1          amd64        Open VMware Tools for virtual machines hosted on VMware (CLI)

こんな感じで open-vm-tools がインストールされていれば OK です

最後に

packer の VMware Builder を使って vmx ファイルと vmdk ファイルを作成してみました
前回試した AWS 上に AMI を作成する手順よりも、今回のほうが実行するまでの環境を準備するのが結構大変でした

vmx ファイルではなく ovf ファイルを作成することもできるので別に機会に試してみたいと思います

トラブルシューティング

mkdir: can't create directory '/vmfs/volumes/primary': Operation not permitted が出る場合は variables.json の esxi_datastore のパスにちゃんとデータストアのパスが含まれていることを確認しましょう

参考サイト

2017年8月21日月曜日

Ruby で net-ldap を使ってユーザの登録、削除、取得をやってみた

概要

前回 Ruby から ldap を操作してみました
グループの追加まで確認できたので今回はユーザ周りの操作を試してみました

環境

  • CentOS 7.3.1611
  • openldap 2.4.40
  • Ruby 2.3.1p112
  • net-ldap 0.16.0

ユーザを追加する

  • vim user_add.rb
require 'net/ldap'
require 'pp'

PORT = 389
HOST = '192.168.100.5'
BASE = 'dc=example,dc=com'
AUTH = {
  :method => :simple,
  :username => "cn=Manager,#{BASE}",
  :password => 'password'
}

ldap = Net::LDAP.new(
  host: HOST,
  port: PORT,
  base: BASE,
  auth: AUTH
)

raise 'bind failed' unless ldap.bind

dn = "uid=hawk,ou=People,#{BASE}"
attr = {
  :sn => 'hawk',
  :cn => 'snowlog',
  :objectclass => ['posixAccount', 'inetOrgPerson'],
  :displayName => 'hawksnowlog',
  :uid => 'hawk',
  :uidNumber => '1001',
  :gidNumber => '1000',
  :homeDirectory => '/home/hawk',
  :loginShell => '/bin/bash',
  :userPassword => '{CRYPT}E1N/hpjy8pfc6',
  :mail => 'hawk@hawksnowlog.cf'
}
ldap.add(
  :dn => dn,
  :attributes => attr
)
pp ldap.get_operation_result

前半の接続部分は前回のコードをそのまま使用しています
ユーザを追加するための処理は dn の定義からです

ポイントは objectclass を配列で指定しているところ
そうしないと後に定義した objectclass に上書きされてしまいます
もし inetOrgPerson を後に記載した場合 'uidNumber' not allowed と言われ uid の指定ができません

#<OpenStruct extended_response=nil, code=65, error_message="attribute 'uidNumber' not allowed", matched_dn="", message="Object Class Violation">

では、posixAccount だけ objectclass に指定するとそんなクラスはないと言われます

#<OpenStruct extended_response=nil, code=65, error_message="no structural object class provided", matched_dn="", message="Object Class Violation">

結局 objectclass を配列にして posixAccount と inetOrgPerson を一緒に指定する必要がありました

あとは実行してエラーが表示されなければ OK です

  • bundle exec ruby user_add.rb
#<OpenStruct extended_response=nil, code=0, error_message="", matched_dn="", message="Success">

userPassword の部分は slappasswd コマンドで作成した暗号化済みのパスワードになります

ユーザを削除する

  • vim user_delete.rb
dn = "uid=hawk,ou=People,#{BASE}"
ldap.delete(
  :dn => dn
)
pp ldap.get_operation_result

削除は dn を指定するだけなので簡単です
add のときと同じように結果が返ってくれば OK です

ユーザを取得する

  • vim user_search.rb
users = ldap.search(base: "ou=People,#{BASE}")
pp users

で以下の表に表示されれば OK です

[#<Net::LDAP::Entry:0x000000015a61e8
  @myhash=
   {:dn=>["ou=People,dc=example,dc=com"],
    :objectclass=>["organizationalUnit"],
    :ou=>["People"]}>,
 #<Net::LDAP::Entry:0x000000015a4258
  @myhash=
   {:dn=>["uid=hawk,ou=People,dc=example,dc=com"],
    :sn=>["hawk"],
    :cn=>["snowlog"],
    :objectclass=>["posixAccount", "inetOrgPerson"],
    :displayname=>["hawksnowlog"],
    :uid=>["hawk"],
    :uidnumber=>["1001"],
    :gidnumber=>["1000"],
    :homedirectory=>["/home/hawk"],
    :loginshell=>["/bin/bash"],
    :userpassword=>["{CRYPT}E1N/hpjy8pfc6"],
    :mail=>["hawk@hawksnowlog.cf"]}>]

特定のグループに所属するユーザを取得する

ということをしたいケースはよくあると思います
search の際に filter という機能を使うことで実現できました
自分は gidNumber を指定してその gidNumber を持つユーザを取得する方法で実現しましたが、他にもやり方はいろいろとあると思います

group = "group1"
groups = ldap.search(base: "cn=#{group},ou=Group,#{BASE}")
groups.each { |group|
  gidNumber = group['gidNumber'][0]
  group_name_filter = Net::LDAP::Filter.eq( "gidNumber", "#{gidNumber}" )
  users = ldap.search(base: "ou=People,#{BASE}", filter: group_name_filter, return_result: true)
  pp users
}

ちょっと複雑なように見えますが一旦 group 名から gidNumber を取得して、取得した gidNumber を元に再度 search をかけています
ポイントは取得した groups をループしたときのクラスが Net::BER::BerIdentifiedArray というクラスなのですが配列になっているので、先頭だけを取得して使っています
今回はグループに 1 つの gidNumber しか持たせていないので先頭を無条件で使っていますが、複数の gidNumber を持つグループの場合は少し考慮が必要になります

最後に

Ruby + net-ldap を使って ldap に基本的なユーザ操作をしてみました
特につまるところはなかった感じです
今回作成したユーザは posixAccount として作成してるので Linux などから SSH ログインすることも可能です

2017年8月20日日曜日

Ruby で ldap を使ってみる

概要

Ruby に net-ldap というライブラリがあり、これを使うと Ruby から ldap の操作ができます
今回は初期設定と接続まで行ってみました
ldap サーバの構築に関しては過去の記事を元にしています

環境

  • CentOS 7.3.1611
  • openldap 2.4.40
  • Ruby 2.3.1p112
  • net-ldap 0.16.0

事前準備

過去の ldap 構築記事で admin のパスワード設定、基本スキーマの登録、dc の登録、 ou の登録までは手動で済ませておいてください
もしかするとこれも Ruby から出来るかもですが今回は触れません

ライブラリインストール

  • bundle init
  • vim Gemfile
gem "net-ldap"
  • bundle install --path vendor/bundle

ldap に接続する

  • vim connect.rb
require 'net/ldap'
require 'pp'

PORT = 389
HOST = '192.168.100.5'
BASE = 'dc=example,dc=com'
AUTH = {
  :method => :simple,
  :username => "cn=Manager,#{BASE}",
  :password => 'password'
}

ldap = Net::LDAP.new(
  host: HOST,
  port: PORT,
  base: BASE,
  auth: AUTH
)

raise 'bind failed' unless ldap.bind

ldap は SSL を使っていないので 389 を指定します
HOST の部分は ldap サーバの IP または URL を入力してください

  • bundle exec ruby connect.rb

でエラーがでなければ OK です

グループを操作してみる

せっかくなのでもう少し操作してみます
まずグループを追加してみましょう
先ほどの接続ロジック後に以下を記載してください

  • group_add.rb
dn = "cn=group1,ou=Group,#{BASE}"
attr = {
  :cn => 'group1',
  :objectClass => 'posixGroup',
  :gidNumber => '1000'
}
ldap.add(
  :dn => dn,
  :attributes => attr
)
pp ldap.get_operation_result

これで #<OpenStruct extended_response=nil, code=0, error_message="", matched_dn="", message="Success"> と表示されれば OK です
作成されたグループ名は group1 になり、ou=Group に属しています

最後の get_operation_result は結構便利でエラーの場合、詳細が見れるのでこれで確認すると良いと思います

そしたらグループの一覧を取得してみましょう

  • group_search.rb
groups = ldap.search(base: "ou=Group,#{BASE}")
pp groups

で以下のように表示されれば OK です

[#<Net::LDAP::Entry:0x000000023d3ec8
  @myhash=
   {:dn=>["ou=Group,dc=example,dc=com"],
    :objectclass=>["organizationalUnit"],
    :ou=>["Group"]}>,
 #<Net::LDAP::Entry:0x000000023d3130
  @myhash=
   {:dn=>["cn=group1,ou=Group,dc=example,dc=com"],
    :cn=>["group1"],
    :objectclass=>["posixGroup"],
    :gidnumber=>["1000"]}>]

あるグループだけ取得したい場合は以下のようにします

groups = ldap.search(base: "ou=Group,#{BASE}")

最後に

Ruby から ldap が操作できる net-ldap を使ってみました
ldap 自体クセが強いのでまずそれに慣れることからかなと思います

登録系に関しては ldif のフォーマットをそのまま Ruby のハッシュに落とし込んで実行するだけという感じです
取得系に関しては面倒くさいコマンドを発行する必要がなく取得できるのはうれしいです
ただ、クエリというか検索の条件は ldap のフォーマットを使わないといけないのが残念なところです

他の ldap 用のライブラリもあるのでそれを使えばもう少し簡単に使えるやつもあるかもしれません

参考サイト

2017年8月19日土曜日

SpriteKit で物理エンジンを使ってみる

概要

前回 SKAction を使って SKNode を動かしてみました
SpriteKit には協力な物理エンジンが備わっておりデフォルトで簡単に使用することができます
今回は物理エンジン (SKPhysicsBody) を使って SKNode に重力を与えてみました

環境

  • macOS X 10.12.6
  • Xcode 8.3.3 (8E3004b)

SKNode を配置する

GameScene.sks を編集しましょう
前回から以下のような感じで SKNode を配置しましょう
physicsbody1.png

block1 が転がって最終的に地面に着地します

GameScene.swift の編集

didMove を以下のように編集します

override func didMove(to view: SKView) {
    // 物理エンジンのグローバル設定
    self.physicsBody = SKPhysicsBody.init(edgeLoopFrom: self.frame)
    // block1
    let block1 = self.childNode(withName: "block1") as? SKSpriteNode
    block1?.physicsBody = SKPhysicsBody(texture: (block1?.texture!)!, size:(block1?.size)!)
    // block2
    let block2 = self.childNode(withName: "block2") as? SKSpriteNode
    block2?.physicsBody = SKPhysicsBody(rectangleOf: (block2?.frame.size)!)
    block2?.physicsBody?.isDynamic = false
    // block3
    let block3 = self.childNode(withName: "block3") as? SKSpriteNode
    block3?.physicsBody = SKPhysicsBody(rectangleOf: (block3?.frame.size)!)
    block3?.physicsBody?.isDynamic = false
}

まず SKPhysicsBody.init で物理エンジンの枠を指定します
今回は画面を枠にすることでノードが画面外にいかないようにしています

そして各ノードを取得して physicsBody で物理エンジンを指定します
block1 は落下させるため SKPhysicsBody(texture:) を設定します
他の 2 つは固定させて置いておくだけなので SKPhysicsBody(rectangleOf:) を設定し isDynamic を false に設定します
こうすることで物理エンジンは適用するものの重力の影響は受けなくなります
こうすることで block1 が block2, 3 に衝突する感じにすることができます

動作確認

では実行して確認してみます
以下のように block1 が転がって最終的には地面に到着すれば OK です
physicsbody2.gif

最後に

SpriteKit の物理エンジンを使ってみました
ノードを取得してプロパティに SKPhysicsBody を設定するだけなので非常に簡単です

物理エンジンの当たり判定 (Body Type) や衝突後の動きの変更などいろいろなカスタマイズもできるので試してみると良いと思います
設定は .sks だけでできるものも多いです

参考サイト

2017年8月18日金曜日

SKAction を使って SKNode を動かしてみた

概要

前回 SpriteKit の簡単な使い方を紹介してみました
今回は SKAction を使って SKNode をいろいろと動かしてみたいと思います
環境は前回のものをそのまま利用します

環境

  • macOS X 10.12.6
  • Xcode 8.3.3 (8E3004b)

SKNode の追加

GemeScene.sks を編集して更に SKSpriteNode を追加しましょう
追加したらインスペクターから以下を編集します
[] は入力している値です

  • Sprite -> Name -> [block2]
  • Sprite -> Texture -> [block2]
  • Sprite -> Position -> Z -> 1

こんな感じで block3 も追加しましょう
以下のような感じになれば OK です
skaction1.png

GameScene.swift でノードを動かす

では 3 つのノードを動かしましょう
GameScene.swift の didMove を以下のように編集します

override func didMove(to view: SKView) {
    // block1
    let block1 = self.childNode(withName: "block1") as? SKSpriteNode
    let action1 = SKAction.moveTo(x: self.frame.width, duration: 1.0)
    let action2 = SKAction.moveTo(x: -(self.frame.width), duration: 1.0)
    let forever1 = SKAction.repeatForever(SKAction.sequence([action1, action2]))
    block1?.run(forever1)
    // block2
    let block2 = self.childNode(withName: "block2") as? SKSpriteNode
    let action3 = SKAction.moveBy(x: 100, y: 100, duration: 1.0)
    block2?.run(action3)
    // block3
    let block3 = self.childNode(withName: "block3") as? SKSpriteNode
    let action4 = SKAction.rotate(toAngle: (CGFloat(45.0 / 180.0 * Double.pi)), duration: 1.0)
    block3?.run(action4)
}

簡単に説明します
まず 3 つのノードを childNode で取得しています
そのあとでアクションをそれぞれ作成します
今回は block1 からそれぞれ move(to:), moveBy, rotate(toAngle:) になります
move(to:) は指定した座標にノードを移動させます
moveBy は現在の座標から指定した座標分移動させます
そして rotate(toAngle:) は指定した角度分回転させることができる Action になります
あとはアクションを作成したらノードの run メソッドに指定すればアクションが実行されます
例えばアクションを無限ループさせたいことがあると思います
その場合は block1 に適用している repeatForever を使うことで実現することができます

動作確認

実行すると以下のように動作します
skaction2.gif

最後に

Swift3 で SKAction を使って SKSpriteNode をいろいろと動かしてみました
他にも SKAction のメソッドはたくさんあるので試してみると良いと思います
https://developer.apple.com/documentation/spritekit/skaction

参考サイト

2017年8月17日木曜日

Swift3 で SpriteKit に入門する

概要

SpriteKit は 2D のゲームを開発するためのフレームワークです
ゲームも作成したいなーと思い入門してみました
基本的なコーディング方法や操作方法について紹介します

環境

  • macOS X 10.12.6
  • Xcode 8.3.3 (8E3004b)

プロジェクトを作成する

今回は SpriteKit を使うため Game を選択しましょう
sprite_kit1.png

最近 GameplayKit 新しいフレームワークも追加されたのですが、今回は使わないので「Integrate GameplayKit」のチェックは外しておきます
sprite_kit2.png

あとはプロジェクトを作成するディレクトリを選択すれば OK です

開発準備

今回は Xcode が作成してくれた .sks ファイルと .swift ファイルをそのまま使っていきます
他のサイトなどを見ると .sks ファイルを使わないで .swift だけで開発する手法が紹介されていますが個人的には UI とロジックは分けて書きたいので、そのまま使う方法でいきます

とりあえず今回は SKNode と呼ばれるオブジェクトを 1 つ配置してそれを swift ファイルから操作してみます
オブジェクト用のアイコンを用意しましょう
適当なアイコンで OK です
assets.atlas というディレクトリにアイコンを保存してください
spkit3.png

そしてこれをプロジェクトに追加します
フォルダごとドラッグアンドドロップすれば OK です
コピー時の選択は以下の通りです
spkit4.png

以下の追加できれば OK です
spkit5.png

SKNode を追加する

GameScene.sks を開きましょう
すると「helloLabel」というラベルがあるので一旦これを削除します (あっても問題ないです)

右ペインでインスペクターを選択し「Color Sprite」を選択し Scene 配下にドラッグアンドドロップして追加します
すると右側のサンプルにもオブジェクトが表示されるので適当に位置を調整しましょう
こんな感じになれば OK です
spkit7.png

そしたら右ペインのインスペクターで Texture の欄を選択し先ほど追加した assets.atlas 内にある画像のファイル名を選択しましょう
ちゃんと assets.atlas が追加されている場合プルダウンの中に選択肢が登場するはずです
こんな感じで SKSpriteNode が画像に変更されれば OK です
spkit8.png

さらに Name という欄があるのでここに追加したノードの名前を入力しましょう
ここで入力した名前は Swift 側で参照する際の識別子として使われるので他のノードと被らないようにかつわかりやすい名前を付与しましょう
今回は画像と同じ名前の「block1」としました
Name 欄を変更すると左ペインの Scene 側の表示も Name のものに変わると思います

そして更に Scene に Custom Class を設定しましょう
Scene のインスペクターに移動しここの Custom Class の欄にすでにある GameScene.swift を参照するために「GameScene」を入力します
spkit9.png

Swift 側で参照する

では追加したノードを Swift 側で参照しましょう

GameViewController.swift の編集

まず GameViewController に .sks に指定した Custom Class を指定します
GameViewController の viewDidLoad でシーンを呼び出す部分があります
そこの SKScene を GameScene に変更します

override func viewDidLoad() {
    super.viewDidLoad()

    if let view = self.view as! SKView? {
        // Load the SKScene from 'GameScene.sks'
        if let scene = GameScene(fileNamed: "GameScene") {
            // Set the scale mode to scale to fit the window
            scene.scaleMode = .aspectFill

            // Present the scene
            view.presentScene(scene)
        }

        view.ignoresSiblingOrder = true

        view.showsFPS = true
        view.showsNodeCount = true
    }
}

GameScene.swift の編集

GameScene.swift を開きましょう
デフォルトでいろいろと記載があると思いますが、今回はそのままで追記していきます
とりあえず今回は .sks 側に追加したノードを取得して位置を変更してみます
GameScene.swift の didMove メソッドの最後に以下を追加しましょう
既存のコードでいろいろ書いていますが、それは .sks にもあったラベルを表示する処理などが書かれています

  • GameScene.swift
let block1 = self.childNode(withName: "block1") as? SKSpriteNode
block1?.position = CGPoint(x: 0, y: 0)

ポイントは childNode です
これに .sks で指定したノードの名前を指定することで SKSpriteNode としてノードを取得することができます
あとはそのノードのフィールド (今回であれば position) を編集するだけです
今回は CGPoint で中央指定しています
SpriteKit の場合座標の中心が画面の中心になります
なので 0 以上のプラスを指定した場合ノードは右上へと移動しマイナスを指定すると左下へと移動します

動作確認

実行するとブロックが中央に移動していると思います
spkit10.png

最後に

2D ゲーム開発するために Swift + SpriteKit に入門してみました
2D ゲームを作成するためのフレームワークは他にもたくさんありますが、個人的に iOS アプリを出しているということもあり SpriteKit を選択しました

SpriteKit には他にも物理エンジンやノードを動かすための Action が豊富に用意されているので今後それらも紹介できればと思います

参考サイト

2017年8月16日水曜日

packer で AWS に AMI イメージを作成してみた

概要

packer を使って AWS 上に AMI のイメージを自動で作成してみました
AWS 側の VPC 設定などではまりポイントがあったので合わせて紹介します

環境

  • CentOS 7.3.1611
  • packer 1.0.3

packer インストール

https://www.packer.io/downloads.html から Linux 64-bit 版をダウンロードします

  • unzip packer_1.0.3_linux_amd64.zip
  • mv packer /usr/local/bin/

PATH が通っているところに適当に移動してください

  • packer -v
1.0.3

でインストール完了です

テンプレートファイルの作成

作成するイメーを定義するテンプレートファイルを作成します

  • example.json
{
  "variables": {
    "aws_access_key": "",
    "aws_secret_key": ""
  },
  "builders": [{
    "type": "amazon-ebs",
    "access_key": "{{user `aws_access_key`}}",
    "secret_key": "{{user `aws_secret_key`}}",
    "region": "ap-southeast-2",
    "source_ami_filter": {
      "filters": {
        "virtualization-type": "hvm",
        "name": "*ubuntu-xenial-16.04-amd64-server-*",
        "root-device-type": "ebs"
      },
      "owners": ["099720109477"],
      "most_recent": true
    },
    "instance_type": "t2.micro",
    "ssh_username": "ubuntu",
    "ami_name": "packer-example {{timestamp}}",
    "subnet_id": "subnet-4b31382f",
    "security_group_id": "sg-c33edba5"
  }]
}

aws_access_key と aws_secret_key の部分は各自のものを入力してください
あとは subnet_id と security_group_id の部分も各自で作成したものに変更してください

  • packer validate example.json

でエラーがでなければビルドを実行できます

ビルドする

ビルドが成功すると最終的には AMI のイメージが作成されて完了になります
AWS 側の設定ミスで結構エラーが出たので以下にまとめています
packer 側というよりか AWS 側の設定のほうが自分は大変でした

  • packer build example.json
amazon-ebs output will be in this color.

==> amazon-ebs: Prevalidating AMI Name...
    amazon-ebs: Found Image ID: ami-546d7437
==> amazon-ebs: Creating temporary keypair: packer_598a4b64-8997-7032-bcee-a3113fdde266
==> amazon-ebs: Launching a source AWS instance...
    amazon-ebs: Instance ID: i-0eeb422db2057ff58
==> amazon-ebs: Waiting for instance (i-0eeb422db2057ff58) to become ready...
==> amazon-ebs: Adding tags to source instance
    amazon-ebs: Adding tag: "Name": "Packer Builder"
==> amazon-ebs: Waiting for SSH to become available...
==> amazon-ebs: Connected to SSH!
==> amazon-ebs: Stopping the source instance...
    amazon-ebs: Stopping instance, attempt 1
==> amazon-ebs: Waiting for the instance to stop...
==> amazon-ebs: Creating the AMI: packer-example 1502235491
    amazon-ebs: AMI: ami-79e7fe1a
==> amazon-ebs: Waiting for AMI to become ready...
==> amazon-ebs: Terminating the source AWS instance...
==> amazon-ebs: Cleaning up any extra volumes...
==> amazon-ebs: No volumes to clean up, skipping
==> amazon-ebs: Deleting temporary keypair...
Build 'amazon-ebs' finished.

==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs: AMIs were created:

ap-southeast-2: ami-79e7fe1a

エラー集

  • Error launching source instance: VPCResourceNotSpecified: The specified instance type can only be used in a VPC. A subnet ID or ne
    twork interface ID is required to carry out the request.

テンプレート内で subnet_id パラメータを使って vpc を指定する必要があります

  • InvalidSubnetID.NotFound: The subnet ID 'vpc-a08048c4' does not exist

指定したリージョン内に vpc -> subnet が存在しません

  • Error launching source instance: InvalidParameter: Security group sg-0d6ccd6a and subnet subnet-d3fe41a5 belong to different networks.

指定したセキュリティグループに指定した vpc が所属していません

  • Timeout waiting for SSH.

指定したサブネットで「自動割り当てパブリック IP」を有効にする必要があります
指定したサブネットの VPC で「DNS 解決」と「DNS ホスト名」を有効にする必要があります
指定したサブネットの VPC にインターネットゲートウェイをアタッチする必要があります
指定したサブネットの VPC にルートテーブルを新規で作成する必要があります
指定したサブネットの VPC に作成したルートテーブルのルートにインターネットゲートウェイを指定する必要があります (送信先: 0.0.0.0/0, ターゲット: 作成したインターネットゲートウェイ)
指定したサブネットの VPC にサブネット関連付けで新規に作成したサブネットを割り当てる必要があります

タイムアウトのエラーに関しては VPC 内に作成されたインスタンスがインターネットに接続できないためにエラーとなるケースがほとんどです
なので、対象の VPC がインターネットに接続できるようになれば OK です

プロビジョニングしてみる

事前に OS 上に必要なパッケージや設定を行うことができます
先程の example.json にプロビジョニング用の設定を追加してみましょう

vim example_provisioning.json

{
  "variables": {
    "aws_access_key": "",
    "aws_secret_key": ""
  },
  "builders": [{
    "type": "amazon-ebs",
    "access_key": "{{user `aws_access_key`}}",
    "secret_key": "{{user `aws_secret_key`}}",
    "region": "ap-southeast-2",
    "source_ami_filter": {
      "filters": {
      "virtualization-type": "hvm",
      "name": "*ubuntu-xenial-16.04-amd64-server-*",
      "root-device-type": "ebs"
      },
      "owners": ["099720109477"],
      "most_recent": true
    },
    "instance_type": "t2.micro",
    "ssh_username": "ubuntu",
    "ami_name": "packer-example {{timestamp}}",
    "subnet_id": "subnet-4b31382f",
    "security_group_id": "sg-c33edba5"
  }],
  "provisioners": [{
    "type": "shell",
    "inline": [
      "sleep 30",
      "sudo apt-get update",
      "sudo apt-get install -y redis-server"
    ]
  }]
}

variables, builders の部分はそのままで、新たに provisioners の項目を追加しています
今回は shell を使って apt コマンドで redis-server をインストールしているだけです

これもビルドしてみましょう
ビルド中に表示されるデバッグ情報に apt install のログも表示されると思います
AMI ができたあとにその AMI から EC2 インスタンスを作成して SSH ログインしてみます
すると redis がすでにインストールされている状態でインスタンスが作成されるのがわかると思います
一点注意が必要なのが SSH ユーザはテンプレートの ssh_username で定義した ubuntu になります

  • ssh -i "your_key.pem" ubuntu@ec2-xx-xxx-xxx-xxx.ap-southeast-2.compute.amazonaws.com

その他

あとは Getting Started に Parallel Build のやり方や Vagrant Box の作成方法や Atlas と連携する方法が紹介されています
興味があれば触ってみると良いかなと思います

最後に

packer と AWS を連携して AMI を自動生成する方法を紹介しました
今回の場合はどちらかというと AWS 側の設定でハマった部分が多かったです
packer 自体は非常に簡単に使えるので便利かなと思います

次回は VMware 上で動作するベースイメージを作成してみたいと思います

参考サイト