20190506のMacに関する記事は10件です。

カンタン! Mac で 仮想環境構築(VirtualBox + Vagrant + CentOS 7)

1. はじめに

実際に仮想環境を構築しながら、自分が手順や知識を忘れない為に記事に残しています。

2. 仮想環境とは

仮想環境とは、パソコンやサーバーなどのハードウェア内で、仮想化の技術を使って創り出された仮想的な環境を意味します。

今回は、仮想化ソフトウェア(VirtualBox)と仮想マシンを構築するためのソフトウェア(Vagrant)を使って、私のパソコン(Mac)に、仮想的なパソコン(CentOS 7)を創り出します。

3. VirtualBox のインストール

こちらの記事を参考にインストールして下さい。

4. Vagrant のインストール

こちらの記事を参考にインストールして下さい。

5. Vagrant の操作

5 - 1. Box の追加

Boxとは、仮想マシンを作成する際に必要なOSのディスクイメージファイル等が入ったファイルの集まりで、例えて言えば、仮想マシンのテンプレート(雛形)です。

手順1. ターミナルを起動します。

手順2. ホームフォルダの直下に、下記のディレクトリを作成するコマンドを実行して、「MyVagrant」フォルダを作成します。

$ mkdir MyVagrant

手順3. 下記のディレクトリを移動するコマンドを実行して、「MyVagrant」フォルダに移動します。

$ cd MyVagrant

手順4. 下記のディレクトリを作成するコマンドを実行して、「CentOS7」フォルダを作成します。

$ mkdir CentOS7

手順5. 下記のディレクトリを移動するコマンドを実行して、「CentOS7」フォルダに移動します。

$ cd CentOS7

手順6. 下記の Box を追加するコマンドを実行します。

$ vagrant box add centos/7

下記のように、プロバイダを選択して下さいと表示されます。

$ vagrant box add centos/7
==> box: Loading metadata for box 'centos/7'
    box: URL: https://vagrantcloud.com/centos/7
This box can work with multiple providers! The providers that it
can work with are listed below. Please review the list and choose
the provider you will be working with.

1) hyperv
2) libvirt
3) virtualbox
4) vmware_desktop

Enter your choice: 

手順7. 今回は、VirtualBox を使っているので、「3」を入力して実行します。

Enter your choice: 3

Box の追加に成功すると、下記のように表示されます。

==> box: Successfully added box 'centos/7' (v1902.01) for 'virtualbox'!

手順8. 下記の Box を一覧表示させるコマンドを実行します。

$ vagrant box list

下記のように追加した Box が一覧に表示されていれば、Box の追加は完了です。

$ vagrant box list
centos/7 (virtualbox, 1902.01)

5 - 2. Vagrant の初期化

手順1. 下記の Vagrant を初期化するコマンドを実行します。引数には、先ほど追加した Box(centos/7)を指定します。

$ vagrant init centos/7

下記のように、Vagrant を起動する準備ができましたと表示されます。

$ vagrant init centos/7
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.

手順2. 下記のディレクトリやファイルの情報を表示するコマンドを実行します。

$ ls

下記のように、「Vagrantfile」という設定ファイルが作成されていれば、Vagrant の初期化は完了です。

$ ls
Vagrantfile

5 - 3. Vagrant の起動

手順1. 先ほど作成した「Vagrantfile」ファイルが存在するディレクトリで、下記の Vagrant を起動するコマンドを実行します。VirtualBox を使って、仮想マシンが作成された後、Vagrant が起動します。

$ vagrant up

手順2. 下記の仮想マシンの状態を確認するコマンドを実行します。

$ vagrant status

下記のように「running」と表示されていれば、Vagrant の起動は完了です。

起動している状態
$ vagrant status
Current machine states:

default                   running (virtualbox)

The VM is running. To stop this VM, you can run `vagrant halt` to
shut it down forcefully, or you can run `vagrant suspend` to simply
suspend the virtual machine. In either case, to restart it again,
simply run `vagrant up`.

下部の文章は、仮想マシンは実行中です。停止させるには、'vagrant halt'コマンドを実行して強制的にシャットダウンするか、'vagrant suspend'コマンドを実行して仮想マシンを一時停止させます。どちらの場合も、再起動するには、'vagrant up'コマンドを実行してください、というような内容です。

5 - 4. ログイン

手順1. 下記の仮想マシンにSSH接続するコマンドを実行します。

$ vagrant ssh

下記のように、「vagrant」ユーザーでログインができます。

[vagrant@localhost ~]$

手順2. 下記の現在、操作しているディレクトリを確認するコマンドを実行します。

[vagrant@localhost ~]$ pwd

下記のように、仮想マシン内のディレクトリを操作していることがわかります。

[vagrant@localhost ~]$ pwd
/home/vagrant

5 - 5. ログアウト

手順1. ログアウトする場合は、下記のコマンドを実行します。

[vagrant@localhost ~]$ exit

下記のように、ログアウトができます。

[vagrant@localhost ~]$ exit
logout
Connection to 127.0.0.1 closed.

5 - 6. Vagrant の停止

手順1. 下記の Vagrant を停止するコマンドを実行します。

$ vagrant halt

下記のように、仮想マシンを正常にシャットダウンしようとしています...と表示されます。

$ vagrant halt
==> default: Attempting graceful shutdown of VM...

手順2. コマンドが入力できるようになったら、下記の仮想マシンの状態を確認するコマンドを実行します。

$ vagrant status

仮想マシンが停止している場合は、下記のように表示されます。

停止している状態
$ vagrant status
Current machine states:

default                   poweroff (virtualbox)

The VM is powered off. To restart the VM, simply run `vagrant up`

下部の文章は、仮想マシンを再起動するには'vagrant up'コマンドを実行して下さいと表示されています。

以上が、Mac で VirtualBox と Vagrant を使って仮想環境を構築する基本的な手順になります。

以下は、その他のよく使う操作の説明になります。

6. その他の操作

6 - 1. 仮想マシンの一時停止

手順1. 下記のコマンドで仮想マシンを一時停止できます。

$ vagrant suspend

下記のように、仮想マシンの状態を保存して実行を中断しています...と表示されます。

$ vagrant suspend
==> default: Saving VM state and suspending execution...

手順2. コマンドが入力できるようになったら、下記の仮想マシンの状態を確認するコマンドを実行します。

$ vagrant status

仮想マシンが一時停止している場合は、下記のように表示されます。

一時停止している状態
$ vagrant status
Current machine states:

default                   saved (virtualbox)

To resume this VM, simply run `vagrant up`.

下部の文章は、仮想マシンを再開するには'vagrant up'コマンドを実行して下さいと表示されています。

6 - 2. 仮想マシンの再起動

手順1. 下記のコマンドで仮想マシンを再起動できます。

$ vagrant reload

以前、ECサイトの開発案件にて、'vagrant reload'コマンドでは jQuery が正常に読み込まれないことがあったので、私は下記のコマンドで仮想マシンを再起動させています。

$ vagrant halt; vagrant up

6 - 3. 仮想マシンの破棄

手順1. 下記のコマンドで仮想マシンを破棄できます。(Box は破棄されません。)

$ vagrant destroy

6 - 3. Box の削除

手順1. 下記のコマンドで Box を削除できます。

$ vagrant box remove centos/7

6 - 4. Box のパッケージ化

手順1. 下記のコマンドで Box をパッケージ化できます。

$ vagrant package

下記のように、「package.box」というファイルが作成されます。

$ ls
Vagrantfile package.box

「package.box」ファイルを指定して、下記のコマンドで Box を追加できます。

$ vagrant box add {box名} package.box

開発メンバーに、自分の「package.box」ファイルを配布することで、同じ環境で開発を行うことができ、とても便利です。

6 - 5. SSH接続情報の確認

手順1. 下記のコマンドで SSH 接続情報の確認ができます。

$ vagrant ssh-config

以上になります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【これだけは】よく使う便利なツール【入れる】

インターネットの中には様々なツールがありますよね。
今日は、その中でよく使っているツールの紹介をします!

キャプチャ

FastStone Capture (Windows)

https://www.vector.co.jp/soft/win95/art/se492849.html

Skitch - 撮る。描き込む。共有する (Mac)

https://itunes.apple.com/jp/app/skitch-%E6%92%AE%E3%82%8B-%E6%8F%8F%E3%81%8D%E8%BE%BC%E3%82%80-%E5%85%B1%E6%9C%89%E3%81%99%E3%82%8B/id425955336?mt=12

どちらも直感的に使用できるUIで、使い勝手が良いです。
エビデンス取得に愛用しています^^

差分表示

WinMerge (Windows)

http://winmerge.org/downloads/?lang=ja
2019-05-06_190514.jpg

FileMerge (Mac)

https://itunes.apple.com/jp/app/xcode/id497799835?mt=12
FileMergeはXcodeの機能の一部です。
skitch.png

ソースのコミット、レビュー時に活用しています。

リモートデスクトップ

TeamViewer

https://www.teamviewer.com/ja/
skitch (1).png
遠隔操作のツールです。
Windows→Mac、Mac→Windowsの操作にも対応しています。
何気に便利な機能。

画像加工

Ralpha

https://forest.watch.impress.co.jp/library/software/ralpha/
2019-05-06_190425.jpg

画像のリサイズからロゴの透かし挿入まであらゆる加工が可能です。
フォトショより気軽にそれっぽい画像が作成できます。

トラックパッド操作

BetterTouchTool (Mac)

https://folivora.ai/downloads/
skitch (2).png

トラックパッドジェスチャーでショートカットの設定ができます。
「新規タブで開く」や「タブ移動」などブラウジング操作などなど
いろいろなショートカットを設定して置くことができます。

ぶっちゃけこれが一番使用しています!

便利なサイト

Thimble

https://thimble.mozilla.org/ja/
skitch (2).png

オンラインHTMLエディタです。
コード記入後に即、結果を確認できます。


カラーコード一覧表

http://www.netyasun.com/home/color.html
skitch (3).png
CSS、その他エクセル等のカラーコード調整に使用します。
なかなかコードだけで色をイメージすることって難しいですよねー


ネーミング

https://codic.jp/engine
skitch (9).png
変数名、メソッド名の手助けになります。
日本語→英語に変換し、キャメルケース等に変換してくれます。
メソッド名は、全てこれを使用しています。


URLエンコード・デコードフォーム

https://www.tagindex.com/tool/url.html
skitch (12).png
文字列をデコード、エンコードしてくれます。
パラメータとかエンコードされていることが多いので、デバッグ中によく使用します。


あとはChromeやeclipseとかもよく使用しますね。
もはや当たり前の存在になって来ていますので、省略しますm(_ _)m

コメントでも普段使っているツールを教えていただけるとありがたいです。

Windowsの方が便利なツールが揃っている気が…?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker for mac のエラー「unauthorized: incorrect username or password.」対処法

Docker Desktop for mac をインストール後のPullで「unauthorized: incorrect username or password.」が発生。少しハマったので備忘として共有します。

Docker Hubより「pull」時にエラー発生

$docker run hello-world

Unable to find image 'hello-world:latest' locally
docker: Error response from daemon: Get https://registry-1.docker.io/v2/library/hello-world/manifests/latest: unauthorized: incorrect username or password.
See 'docker run --help'.

ID/PWDが異なるとの警告。原因はDockerHub登録時に利用したメールアドレスでログインしていたことでした。
メールアドレスで、DockerHubにログインする必要があります。

[対応]Docker HubにユーザIDでログインする。

image.png

メールアドレスではなくユーザIDでログインしてください!!

確認

DockerHubからPull出来ることを確認。

$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
1b930d010525: Pull complete 
Digest: sha256:92695bc579f31df7a63da6922075d0666e565ceccad16b59c3374d2cf4e8e50e
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonでオリジナルのコマンドを実装してみる【後編】

前回の続きです。

【前編】
https://qiita.com/K_Takata/items/3c0e632db7b662f88f57

こちらの記事で自動生成されているコマンドラインの起点……

jupyter.py
#!/anaconda3/bin/python

# -*- coding: utf-8 -*-
import re
import sys

from jupyter_core.command import main

if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
    sys.exit(main())

の生成方法についてすっ飛ばしていたのですが、結構重要なポイントなのでこちらで解説させていただきます。

目標

前回と同様です。
macまたはLinuxのコマンドラインで

$ MyCommand

と打ち込むと

ジャンプで連載中のチェンソーマンが好き

と出力されるようになる。

$ MyCommand --help

と打ち込むと、

ジャンプで連載中のチェンソーマンをダイレクトマーケティングするコマンド

と、MyCommandの説明が出力される。

↑こちらのコマンドの起点を自動生成してみましょう。
ついでに後半でちょっと複雑なコマンドを作ってみましょう。

よくわかる?解説

ローカルにテキトーにcloneしてくれや!
https://github.com/kizashitakata/MyCommand

よくわからなかったらテキトーに参考にしながら作ってください。
解説しますね。

構造はこんな感じです。

setup.pyを見てみましょう。

├── App
│   ├── __init__.py
│   ├── command1.py
│   └── command2.py
├── README.md
└── setup.py
setup.py
from setuptools import setup, find_packages
setup(
    name='MyCommand',
    version='0.1.0',
    entry_points={'console_scripts': ['MyCommand = App.command1:main']},
    install_requires=[
        'click'
    ],
    packages=find_packages()
    )

今回必要な最低限のみ記載しています。

nameとversionは実際にPyPIにアップして世界中のみんなに使ってもらう際に重要になるのですが、自分で使う分にはテキトーで大丈夫です。PyPIにアップする際にテキトーなバージョンでアップすると後で痛い目に会うので注意しましょう。

今回の内容で一番重要なのが
entry_points
の部分で、ここにコマンドの起点を定義します。
後々わかるので大丈夫です。

install_requiresはなんとなく分かると思いますが、依存モジュールで、ここに定義しておけば後はinstall時になんとなく一緒に持ってきてくれます。

今回はワイ推奨パッケージ便利なコマンドラインパーサのclickを使っているので、依存モジュールとして定義しています。
packages=find_packages() は、__init__.py があるフォルダをパッケージとして認識するために必要です。
……ちょっとややこしいですね。

ディレクトリ構造に戻りますが

├── App
│   ├── __init__.py
│   ├── command1.py
│   └── command2.py
├── README.md
└── setup.py

この__init.py__ は、見ていただければ分かるのですが空ファイルです。
何のためにこの世に存在しているのかというと、"App" フォルダがパッケージだよという証明……みたいなものです。

さっきの
entry_points={'console_scripts': ['MyCommand = App.command1:main']}
の部分での指定は「Appパッケージのcommand1.pyのmainメソッドがコマンドの起点ですよー」くらいの意味です。
「重要なフォルダには __init.py__ を用意するんやで」 くらいの認識でもOKです。

ということでcommand1.pyを見てみましょう。

command1.py
import click

@click.command(help='ジャンプで連載中のチェンソーマンをダイレクトマーケティングするコマンド')
def main():
    click.echo('ジャンプで連載中のチェンソーマンが好き')
    pass

前編の実装よりめっちゃシンプルになりましたね。
それもそのはず、
$ MyCommand --help
の出力内容がclickライブラリの標準機能にまとめられてスッキリシンプルになりました。

出力もちょっと豪華になります。

$ mycommand --help
Usage: mycommand [OPTIONS]

  ジャンプで連載中のチェンソーマンをダイレクトマーケティングするコマンド

Options:
  --help  Show this message and exit.

ちなみに前回の実装方法にせよ今回の実装方法にせよ、コマンドは大文字でも小文字でもどっちでもOKです。今回は小文字で統一します。

さて、いよいよ。
どうやってこいつをコマンドとして自動生成するのか。

ローカルでsetup.pyのあるディレクトリにcdしたあと、以下のように打ち込むだけです。

$ pip install .

.はカレントディレクトリの意味なので、要するにpip installで指定のディレクトリにあるsetup.pyを叩いています。

pip install 〇〇 は便利なもので、PyPIにあるパッケージを指定するとPyPIから持ってきてくれますし、ローカルディレクトリのsetup.pyのあるディレクトリを指定すると、よしなにパッケージを展開してくれます。

コマンド実行後、mycommandが使えるようになってるはずです。
簡単ですね。

ちょっとだけ応用

mycommand one mycommand two みたいに、コマンドごとに処理を変えたいってこともありますよね?
夕飯にカレーが食べたくなるくらいの頻度であるはずです。

setup.py
entry_points={'console_scripts': ['MyCommand = App.command1:main']}
のcommand1の部分をcommand2に書き換えてみましょう。

それから改めて、

$ pip install .

してみてください。

command2の内容はこんな感じです。

command2.py
import click

@click.group()
def main():
    pass

@main.command()
def one():
    click.echo('処理1')

@main.command()
def two():
    click.echo('処理2')

チェンソーマン絡ませるとややこしいのでめっちゃ単純にしました。
これだけです。これだけで、

$ mycommand one
処理1
$ mycommand two
処理2

実装完了です。
夢が広がりますね。

まとめ

pipは優秀。

以上です。お読みいただきありがとうございました。

参考

Pythonでグローバルコマンドを含んだパッケージを作る
https://qiita.com/fetaro/items/bb0eb8292127b5d1e9a8

Python: コマンドラインパーサの Click が便利すぎた
https://blog.amedama.jp/entry/2015/10/14/232045

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Mac]Zshでoh-my-zshからPreztoに切り替える

はじめに

シェル環境はZsh派ということもあり、これまで環境をイイ感じに構築してくれる oh-my-zsh を入れていました。

が、oh-my-zsh インストール後、環境設定もほぼデフォルトの状態で使用。
メンテナンスも満足にしておらず、当時のインストール時の備忘録も残さないまま、勢いで入れてそのままズルズル使用していたので、一発奮起して Prezto に乗り換えてみます。
Prezto - Instantly Awesome Zsh

先人の知恵でたくさん記事があります。参考にさせていただきました。
https://blog.andooown.dev/post/2018/02/prezto/
https://qiita.com/Angelan1720/items/60431c85592fe90fcdd5
https://dev.classmethod.jp/tool/zsh-prezto/

oh-my-zsh や Prezto ってそもそも何なの

oh-my-zsh や Prezto は、Zshでラクに環境構築を行うためのフレームワークです。
一から自分で設定することもできますが、こういったものを利用することで、比較的手軽に環境を構築することが可能になります。

oh-my-zsh はオワコンで古い!とかいろんな意見を見ますが、依然人気は人気みたい。機能豊富だしいろいろできるもんね。
9 Best ZSH configuration frameworks as of 2019 - Slant

前提条件

今回の自分の環境です。
この環境下で Prezto に切り替えていきます。

  • ターミナル: iTerm2 を利用
  • Homebrew/Homebrew Cask によるパッケージ管理
  • oh-my-zsh 適用済み
  • Zsh は Homebrew でインストール済み

完全クリーンな状態から Prezto をインストールする場合

概ね以下のような手順になるかと思います。

  • Homebrew をインストール (入っていない場合)
  • Zsh をインストール

Homebrew の使用に関しては好みがあると思うので、必須ではないです

前段階における注意点

.zshrc などにカスタムで環境設定を入れている場合は既存のものを流用してもいいかも。
わたしはそこまで環境設定を追加していなかったので、完全にリプレースしました。

ただ有事の場合にロールバックができるように、念のため、設定ファイルとヒストリはバックアップを取っておきます。

-> % mkdir bkdir && mv .zsh* bkdir
-> % ll -A bkdir
total 632
-rw-------  1 user  staff   309K  5  6 10:36 .zsh_history
-rw-r--r--  1 user  staff   2.9K 10  4  2018 .zshrc

切り替え手順

oh-my-zsh のアンインストール

アンインストールはコマンドがあります。README参照。

-> % uninstall_oh_my_zsh
Are you sure you want to remove Oh My Zsh? [y/N] y
Removing ~/.oh-my-zsh
Looking for original zsh config...
Switching back to bash
Changing shell for user.
Password for user:
Thanks for trying out Oh My Zsh. It's been uninstalled.

アンインストールが完了しました。
ターミナルを再起動すると、 bash になってます。

$ echo $SHELL
/bin/bash

Prezto のインストール

zshに切り替え。

$ zsh

Prezto のREADMEに書いてあるように、リポジトリをcloneしてきます。

$ git clone --recursive https://github.com/sorin-ionescu/prezto.git "${ZDOTDIR:-$HOME}/.zprezto"

以下のコマンドを実行すると、各種設定ファイルのシンボリックリンクが生成されます。
この時点ですでに存在しているファイルがあると、そのファイルに関してはリンクが生成されません。

$ setopt EXTENDED_GLOB
for rcfile in "${ZDOTDIR:-$HOME}"/.zprezto/runcoms/^README.md(.N); do
  ln -s "$rcfile" "${ZDOTDIR:-$HOME}/.${rcfile:t}"
done
lrwxr-xr-x  1 user  staff      37  5  6 11:01 /Users/userDir/.zlogin -> /Users/userDir/.zprezto/runcoms/zlogin
lrwxr-xr-x  1 user  staff      38  5  6 11:01 /Users/userDir/.zlogout -> /Users/userDir/.zprezto/runcoms/zlogout
lrwxr-xr-x  1 user  staff      40  5  6 11:01 /Users/userDir/.zpreztorc -> /Users/userDir/.zprezto/runcoms/zpreztorc
lrwxr-xr-x  1 user  staff      39  5  6 11:01 /Users/userDir/.zprofile -> /Users/userDir/.zprezto/runcoms/zprofile
lrwxr-xr-x  1 user  staff      37  5  6 11:01 /Users/userDir/.zshenv -> /Users/userDir/.zprezto/runcoms/zshenv
lrwxr-xr-x  1 user  staff      36  5  6 11:03 /Users/userDir/.zshrc -> /Users/userDir/.zprezto/runcoms/zshrc

.zshrc の設定を流用したい場合

いったん既存ファイルは退避しておいて、差分だけをあとから Prezto の .zshrc に追記する形がいいかも。

試しにdiff取ってみましたが、なんだこれ。。
Prezto は超絶シンプルすぎる環境設定。
中略部分は何十行も oh-my-zsh の設定が入ってる部分です。
やっぱり oh-my-zsh はオーバースペックすぎたかな。

あと .zshrc にベタで書き込まれるパラメーターが多い。
oh-my-zsh のパラメーターファイルみたいなのがどっかにあって、それをincludeするとシンプルになりそうな気がする (主観です

$ diff -u ~/bk_zsh/.zshrc ~/.zshrc                                                  
--- /Users/user/bk_zsh/.zshrc   2018-10-04 13:38:32.000000000 +0900
+++ /Users/user/.zshrc  2019-05-06 10:53:24.000000000 +0900
@@ -1,92 +1,13 @@
-# Path to your oh-my-zsh installation.
-export ZSH=/Users/user/.oh-my-zsh
-
-# Set name of the theme to load.
-# Look in ~/.oh-my-zsh/themes/
-# Optionally, if you set this to "random", it'll load a random theme each
-# time that oh-my-zsh is loaded.
-ZSH_THEME="candy"

 ・・・ 中略 ・・・

+# Executes commands at the start of an interactive session.
+#
+# Authors:
+#   Sorin Ionescu <sorin.ionescu@gmail.com>
+#
+# Source Prezto.
+if [[ -s "${ZDOTDIR:-$HOME}/.zprezto/init.zsh" ]]; then
+  source "${ZDOTDIR:-$HOME}/.zprezto/init.zsh"
+fi
+# Customize to your needs...

インストールが完了したので、ログインシェルを Homebrew の Zsh に変更し、ターミナルを再起動します。

$ chsh -s /usr/local/bin/zsh

補足:ログインシェル変更時のエラー

ログインシェルを変更する時に以下のように non-standard shellとエラーが出てしまう場合。
/etc/shells に Homebrew の Zsh のパスを追記することでエラーを解消することができます。

$ chsh -s /usr/local/bin/zsh
Changing shell for user.
Password for user:
chsh: /usr/local/bin/zsh: non-standard shell
/etc/shells
# List of acceptable shells for chpass(1).
# Ftpd will not allow users to connect who are not using
# one of these shells.

/bin/bash
/bin/csh
/bin/ksh
/bin/sh
/bin/tcsh
/bin/zsh
/usr/local/bin/zsh  // Homebrew の Zsh のパスを追記

再起動後にシェルが変更されていることを確認します。

~ ❯❯❯ echo $SHELL
/usr/local/bin/zsh

oh-my-zsh で入ってたaliasのバックアップ忘れた…と思ったけど、 Prezto インストール後のalias確認したら普段よく使うコマンドの短縮形は入ってるから問題なさそう

テーマの設定

さて、肝心のテーマですが、Prezto にはpromptコマンドというものがあり、オプション -p をつけることでテーマのプレビューが表示されます。

~ ❯❯❯ prompt -p

変更後のイメージがしやすくて良い。

2019-05-06 14-27-54.png

powerlineの人をけっこう見かけるのでpowerlineが人気なのかな。
2019-05-06 14-30-37.png

でもわたしはparadoxにします。
デフォルトがsorinになっているので、 .zpreztorc を編集してコメントアウトor上書きします。

~/.zpreztorc
#zstyle ':prezto:module:prompt' theme 'sorin'
zstyle ':prezto:module:prompt' theme 'paradox'

どのようなテーマがあるかはここを見るとわかりやすいかも。
Customizing Your Prezto Prompt - Mike Buss

テーマ適用時の注意点

上記記事のNoteにyou’ll need a Powerline-patched font.と書いてある通り、paradoxをテーマとして適用する時は、Powerline用のフォントを使わないといけないみたいです。

Powerline用のフォントについてはこちら。
GitHub - powerline/fonts: Patched fonts for Powerline users.

Powerline用のフォントの適用

ということで、わたしは Source Code Pro を選択しました。
フォントのインストール方法はgit cloneだったり諸方法あるのですが、わたしは brew-cask-fonts が入っている環境なので、これを使ってインストールします。

❯ brew cask info font-source-code-pro-for-powerline                                 [12:28:41]
font-source-code-pro-for-powerline: latest
https://github.com/powerline/fonts/tree/master/SourceCodePro
Not installed
From: https://github.com/Homebrew/homebrew-cask-fonts/blob/master/Casks/font-source-code-pro-for-powerline.rb

 ・・・以下略・・・

❯ brew cask install font-source-code-pro-for-powerline

インストールできたら、 iTerm2 のフォントの設定で Source Code Pro for Powerline を選択して完了です。
2019-05-06 14-45-09.png

おわりに

とりあえずこれで使ってみて、もうちょっと便利にしたいなーと思ったら適宜調整していきます。
あと oh-my-zsh の残骸もお掃除しないと…

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iPhoneで撮影した画像(*.HEIC)をjpgやpngに一括変換する

iPhoneで撮影した画像(*.HEIC)をjpgやpngに一括変換する

環境: macOS Mojave (10.14.4)

一個の場合

以下で出来ます。

$ sips --setProperty format jpeg input.heic --out output.jpg

ただこれ、ワイルドカード使えないんですよね(´・ω・`)

複数の場合

最新版のImageMagickを使って一括変換できます。

mogrify -format jpg -quality 80 *.HEIC

ImageMagickでエラーが出た時の対処法

ImageMagickで変換できるかテスト

$ mogrify -format png *.HEIC
mogrify: no decode delegate for this image format `HEIC' @ error/constitute.c/ReadImage/556.

エラーが出た。バージョンを確認する。

$ convert -version
Version: ImageMagick 7.0.8-24 Q16 x86_64 2019-01-18

若干古い
ImageMagickを入れ直します。

$ brew reinstall imagemagick                                かなり時間がかかる
$ convert -version
Version: ImageMagick 7.0.8-43 Q16 x86_64 2019-05-03         新しくなった

このバージョンでは成功しました(`・ω・´)ゞ

サンプル集

pngへの変換

$ mogrify -format png *.HEIC

iPhoneで撮影した画像ですとファイルサイズは8MBほどになります。

jpegへの変換(品質80%)

$ mogrify -format jpg -quality 80 *.HEIC

品質を落としてjpegに変換するとファイルサイズは2MBほどになりました。

jpegへの変換(品質80%、画像サイズ縮小)

$ mogrify -format jpg -quality 80 -resize 25% *.HEIC

さらに画像サイズが大きいので縦x横ともに1/4のサイズにすると約110KBになりました。画像サイズは1008x756。

まとめ

一括変換する時はImageMagickが便利です(`・ω・´)ゞ
ImageMagickなのでUbuntuでも使えると思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

一足遅れて Kubernetes を学び始める - 07. workloads その3 -

ストーリー

  1. 一足遅れて Kubernetes を学び始める - 01. 環境選択編 -
  2. 一足遅れて Kubernetes を学び始める - 02. Docker For Mac -
  3. 一足遅れて Kubernetes を学び始める - 03. Raspberry Pi -
  4. 一足遅れて Kubernetes を学び始める - 04. kubectl -
  5. 一足遅れて Kubernetes を学び始める - 05. workloads その1 -
  6. 一足遅れて Kubernetes を学び始める - 06. workloads その2 -
  7. 一足遅れて Kubernetes を学び始める - 07. workloads その3 -
  8. 一足遅れて Kubernetes を学び始める - 08. discovery&LB その1 -

前回

一足遅れて Kubernetes を学び始める - 06. workloads その2 -にて、DaemonSetとStatefulSet(一部)を学習しました。今回は、StatefulSetの続きとJob,CronJobを学習します。

StatefulSet

sample-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: sample-statefulset
spec:
  serviceName: sample-statefulset
  replicas: 3
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.12
          ports:
            - containerPort: 80
          volumeMounts:
          - name: www
            mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes:
      - ReadWriteMany
      storageClassName: managed-nfs-storage
      resources:
        requests:
          storage: 1Gi

永続的にデータが保存されるかどうか確認します。

pi@raspi001:~/tmp $ k apply -f sample-statefulset.yaml
pi@raspi001:~/tmp $ k exec -it sample-statefulset-0 -- df -h
Filesystem  Size  Used Avail Use% Mounted on
...
192.168.3.35:/home/data/default-www-sample-statefulset-0-pvc-*   15G  1.1G   13G   8% /usr/share/nginx/html
...
pi@raspi001:~/tmp $ k exec -it sample-statefulset-0 touch /usr/share/nginx/html/sample.html

sample.htmlというファイルを作りました。こちらが消えるかどうか確認します。

pi@raspi001:~/tmp $ k delete pod sample-statefulset-0
pi@raspi001:~/tmp $ k exec -it sample-statefulset-0 ls /usr/share/nginx/html/sample.html
/usr/share/nginx/html/sample.html

podを消してセルフヒーリングで復活した後、確認すると、sample.html残っています。

pi@raspi001:~/tmp $ k delete -f sample-statefulset.yaml
pi@raspi001:~/tmp $ k apply -f sample-statefulset.yaml
pi@raspi001:~/tmp $ k exec -it sample-statefulset-0 ls /usr/share/nginx/html/sample.html
/usr/share/nginx/html/sample.html

こちらも残っていますね。OKです。

スケーリング

StatefulSetでは、スケールアウトするときは、インデックスが小さいものから増えていきます。
逆にスケールインするときは、インデックスが大きいものから削除されていきます。
また、1つずつ増減します。そのため、一番始めに作られるPodは、一番最後に削除されることになります。
試してみます。

pi@raspi001:~ $ k get pod | grep sample-statefulset
sample-statefulset-0                      1/1     Running   1          10h
sample-statefulset-1                      1/1     Running   1          10h
sample-statefulset-2                      1/1     Running   1          10h
pi@raspi001:~/tmp $ vim sample-statefulset.yaml # replica:3→4
pi@raspi001:~/tmp $ k apply -f sample-statefulset.yaml
pi@raspi001:~/tmp $ k get pod | grep sample-statefulset
sample-statefulset-0                      1/1     Running             1          10h
sample-statefulset-1                      1/1     Running             1          10h
sample-statefulset-2                      1/1     Running             1          10h
sample-statefulset-3                      0/1     ContainerCreating   0          6s
pi@raspi001:~/tmp $ vim sample-statefulset.yaml # replica:4→2
pi@raspi001:~/tmp $ k apply -f sample-statefulset.yaml
pi@raspi001:~/tmp $ k get pod | grep sample-statefulset
sample-statefulset-0                      1/1     Running       1          10h
sample-statefulset-1                      1/1     Running       1          10h
sample-statefulset-2                      1/1     Running       1          10h
sample-statefulset-3                      0/1     Terminating   0          2m4s
pi@raspi001:~/tmp $ k get pod | grep sample-statefulset
sample-statefulset-0                      1/1     Running       1          10h
sample-statefulset-1                      1/1     Running       1          10h
sample-statefulset-2                      0/1     Terminating   0          10h

期待通りですね。1つずつではなく、並列して作成したい場合は、spec.podManagementPolicyをparallelにすれば実現できます。

アップデート戦略

戦略は2通りあり、OnDeleteとRollingUpdateがあります。前者は、削除された(マニュフェスト更新ではなく、delete)タイミングに更新され、後者は、即時更新します。StatefulSetの更新では、アップデート中の過不足分の調整(maxUnavailable, maxSurge)は一切できません。また、partitionというフィールドのによって、どのインデックス以降を更新するかを調整することもできます。これは、ステートフルならではの機能です。
Deploymentでは試してませんでしたが、こちらで試してみようと思います。

デフォルトの戦略はRollingUpdateです。これは何度も動作して確認できているので、OnDeleteを試そうと思います。(partitionは置いとく)

sample-statefulset.yaml
...
spec:
  updateStrategy:
   type: OnDelete
...
  template:
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.13
...

アップデート戦略をOnDeleteにし、nginxイメージを1.12から1.13に更新しました。

pi@raspi001:~/tmp $ k delete -f sample-statefulset.yaml
pi@raspi001:~/tmp $ k apply -f sample-statefulset.yaml
pi@raspi001:~/tmp $ k describe pod sample-statefulset-0 | grep "Image:"
    Image:          nginx:1.12
pi@raspi001:~/tmp $ k delete pod sample-statefulset-0
pi@raspi001:~/tmp $ k get pod | grep sample-statefulset
sample-statefulset-0                      0/1     ContainerCreating   0          5s
sample-statefulset-1                      1/1     Running             0          2m59s
pi@raspi001:~/tmp $ k describe pod sample-statefulset-0 | grep "Image:"
    Image:          nginx:1.13

期待通りですね。明示的にpodを削除すればnginxが更新されました。

Job

一度限りの処理を実行させるリソース。
replicaSetのように複製ができる。
バッチ処理に向いている。

10秒sleepするだけのjobを実行してみます。

sample-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: sample-job
spec:
  completions: 1
  parallelism: 1
  backoffLimit: 10
  template:
    spec:
      containers:
      - name: sleep-container
        image: nginx:1.12
        command: ["sleep"]
        args: ["10"]
      restartPolicy: Never
pi@raspi001:~/tmp $ k apply -f sample-job.yaml
pi@raspi001:~/tmp $ k get pod
NAME                                      READY   STATUS      RESTARTS   AGE
sample-job-d7465                          0/1     Completed   0          3m17s
pi@raspi001:~/tmp $ k get job
NAME         COMPLETIONS   DURATION   AGE
sample-job   1/1           27s        4m8s

jobの実行が終わると、podが消えていますね。そして、jobのCOMPLETIONSが1/1になっているので正常終了したみたいです。逆に正常終了しなかった場合、restartPolicyに沿って再実行することになります。種類としてNeverとOnFailureがあります。Neverは、新規にPodを作って再実行、OnFailureは、既存Podを使って再実行するそうです。ただし、データ自体は消失することになるので、ご注意下さい。

completionsは目標成功数で、parallelismは並列数、backoffLimitは失敗許容値です。
目的に合う設定にすれば良いですね。
また、completionsを未指定にするとjobを止めるまでずっと動き続けます。backoffLimitを未指定にすると6回までとなります。

んー、特に興味が惹かれることもなく、終わります。笑

CronJob

Jobをスケジュールされた時間で実行するリソース。
DeploymentとReplicaSetの関係と似ていて、Cronjobがjobを管理する。

1分毎に50%の確率で成功するjobを用意して、試してみます。

sample-cronjob.yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: sample-cronjob
spec:
  schedule: "*/1 * * * *"
  concurrencyPolicy: Allow
  startingDeadlineSeconds: 30
  successfulJobsHistoryLimit: 5
  failedJobsHistoryLimit: 3
  suspend: false
  jobTemplate:
    spec:
      completions: 1
      parallelism: 1
      backoffLimit: 1
      template:
        spec:
          containers:
          - name: sleep-container
            image: nginx:1.12
            command:
            - "sh"
            - "-c"
            args:
            # 約50%の確率で成功するコマンド
            - "sleep 40; date +'%N' | cut -c 9 | egrep '[1|3|5|7|9]'"
          restartPolicy: Never
pi@raspi001:~/tmp $ k apply -f sample-cronjob.yaml
pi@raspi001:~/tmp $ k get all
NAME                           SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
cronjob.batch/sample-cronjob   */1 * * * *   False     0        <none>          9s

時間がくるまで、job,podは作成されないようです。
数分待ってみました。

pi@raspi001:~/tmp $ k get all
NAME                                          READY   STATUS      RESTARTS   AGE
pod/sample-cronjob-1557115320-dsdvg           0/1     Error       0          2m18s
pod/sample-cronjob-1557115320-qkgtp           0/1     Completed   0          87s
pod/sample-cronjob-1557115380-r57sw           0/1     Completed   0          78s
pod/sample-cronjob-1557115440-2phzb           1/1     Running     0          17s

NAME                                  COMPLETIONS   DURATION   AGE
job.batch/sample-cronjob-1557115320   1/1           105s       2m18s
job.batch/sample-cronjob-1557115380   1/1           52s        78s
job.batch/sample-cronjob-1557115440   0/1           17s        17s

NAME                           SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
cronjob.batch/sample-cronjob   */1 * * * *   False     1        20s             3m12s

名前の命名ルールがあるので、どう関連しているのか一目瞭然ですね。
Podが残っているのは、failedJobsHistoryLimitとsuccessfulJobsHistoryLimitの値の影響ですね。
logで確認できるように残しておくそうですが、ログ収集基盤に集約した方が良いとも言われています。

途中で止めたいときは、spec.suspendをtrueにすることで実現可能になります。
同時実行する制限として、concurrencyPolicyがあり、Allow,Forbid,Replaceがあります。
Allowは、特に制限しない。
Forbidは、前のjobが終わらない限り実行しない。
Replaceは、前のjobを削除し、jobを実行する。

遅延がどのぐらい許容できるかは、startingDeadlineSecondsで指定します。

こちらも、特に何事もなく終わりました。笑

お片付け

pi@raspi001:~/tmp $ k delete -f sample-statefulset.yaml -f sample-job.yaml -f sample-cronjob.yaml
pi@raspi001:~/tmp $ k delete pvc www-sample-statefulset-{0,1,2,3}

終わりに

ようやく、workloadsが終わりました。最後はざっくり進めてしまった感がありました。
次回はこちらです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

mac (Mojave) に DNS サーバを構築する際の覚書

目的

macOS Mojave に brew を用いてローカルネットワーク用 DNS (bind) サーバを構築した際の覚書を記載する

環境

  • PC : Mac mini (Late 2014)
  • OS : macOS Mojave 10.14.2
  • Homebrew : 1.9.0
  • インストールする bind : stable 9.12.3-P1 (bottled)

構築手順

Homebrew で DNS (bind) をインストールする

$ brew search bind
$ brew info bind
$ brew install bind

インストール後の状態確認

$ sudo brew services list # bind の動作状態を確認する
$ sudo brew services stop bind # started 状態ならば一旦停止させる

メイン設定ファイル (/usr/local/etc/named.conf) を設定する

options ステートメントや正引き設定や逆引き設定を記述する

# ex)
...
options {
    directory "/usr/local/var/named"; # ゾーンファイルを格納するディレクトリ
    /*                                                                          
     * If there is a firewall between you and nameservers you want              
     * to talk to, you might need to uncomment the query-source                 
     * directive below.  Previous versions of BIND always asked                 
     * questions using port 53, but BIND 8.1 uses an unprivileged               
     * port by default.                                                         
     */
    // query-source address * port 53;

    version "Unknown DNS Server"; # バージョン表示
    allow-query { localhost; localnets; }; # 問い合わせを受け付けるホスト
    allow-transfer { none; }; # ゾーン転送を許可するホスト
    recursion yes; # 再起問い合わせを受け付けるか
    forwarders { 192.168.0.???; }; # 問い合わせの回送先 DNS サーバ (ルータなどを指定)
    listen-on-v6 { none; }; # IPv6 を受け付けるか
};
...
# sample.net ドメインの正引き設定
zone "sample.net" IN {
     type master;
     file "sample.net.zone";
};

# sample.net ドメインの逆引き設定
zone "0.168.192.in-addr.arpa" IN {
     type master;
     file "0.168.192.zone";
};

ゾーンファイルを設定する

directory に指定したパス (/usr/local/var/named) 配下に,
* 正引き用 : sample.net.zone
* 逆引き用 : 0.168.192.zone
ファイルをそれぞれ作成する

# sample.net.zone (正引き) の設定方法例
$TTL    86400 # 他の DNS サーバがゾーンデータをキャッシュに保存していく時間
$ORIGIN sample.net. # ドメイン名が明治されていないレコードで補完するドメイン名

# SOA はゾーンに関する基本的な情報を記述
# [書式]
#  名前 IN SOA <DNSサーバ> <メールアドレス> (serial;refresh;retry;expire;negative TTL)
# @ はそのドメイン自身を表す
@ IN SOA <DNSサーバ名>.sample.net. root.sample.net. (
                    2019050601 ; serial (変更日+バージョンとしている)                            
                    3H         ; refresh (3時間)                                        
                    15M        ; retry (15分)                                         
                    1D         ; expiry (1日)                                        
                    1D )       ; minimum (1日)
# DNS サーバを FQDN で指定する 
@                  IN NS <DNSサーバ名>.sample.net.
# ホスト名に対する IP アドレスを指定 
<DNSサーバ名>.sample.net. IN A  192.168.0.??? 
# 0.168.192.zone (逆引き) の設定方法例
$TTL    86400
$ORIGIN 0.168.192.in-addr.arpa.

@ IN SOA <DNSサーバ名>.sample.net. root.sample.net. (
          2019050601 ; Serial                                                   
          3H         ; Refresh                                                  
          15M        ; Retry                                                    
          1D         ; Expire                                                   
          1D )       ; Minimum                                                  

                            IN NS  <DNSサーバ名>.sample.net.
# IP アドレスに対するホスト名を記述する
???.0.168.192.in-addr.arpa. IN PTR <DNSサーバ名>.sample.net.

記述方式が正しいか文法チェックする

  • named.conf のチェック方法例
$ /usr/local/sbin/named-checkconf named.conf
  • ゾーンファイルのチェック方法例
# 正引きの場合
$ /usr/local/sbin/named-checkzone sample.net sample.net.zone 
...
OK
# 逆引きの場合
$ /usr/local/sbin/named-checkzone 0.168.192.in-addr.arpa. 0.168.192.zone 
...
OK

bind を起動する

$ sudo brew services start bind
$ sudo brew services list

その他

DHCP サーバの DNS を構築したマシンにする etc

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初めてmacでsshキーを作る

初めての場合

$ssh-keygen -t rsa

これで~/.sshに
秘密鍵id_rsaと公開鍵id_rsa.pubが出来る
ssh認証キーを貼り付けてくれとか頼まれた場合は

$pbcopy .ssh/id_rsa.pub

catで出してコピペする方法もある。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

一足遅れて Kubernetes を学び始める - 06. workloads その2 -

ストーリー

  1. 一足遅れて Kubernetes を学び始める - 01. 環境選択編 -
  2. 一足遅れて Kubernetes を学び始める - 02. Docker For Mac -
  3. 一足遅れて Kubernetes を学び始める - 03. Raspberry Pi -
  4. 一足遅れて Kubernetes を学び始める - 04. kubectl -
  5. 一足遅れて Kubernetes を学び始める - 05. workloads その1 -
  6. 一足遅れて Kubernetes を学び始める - 06. workloads その2 -
  7. 一足遅れて Kubernetes を学び始める - 07. workloads その3 -
  8. 一足遅れて Kubernetes を学び始める - 08. discovery&LB その1 -

前回

一足遅れて Kubernetes を学び始める - 05. workloads その1 -では、Pod,ReplicaSet,Deploymentの3つを学習しました。今回はDaemonSet,StatefulSet(一部)を学びます。

DaemonSet

ReplicaSetとほぼ同じ機能のリソース。
ReplicaSetとの違いは、各ノードに1つずつ配置するのがDaemonSet,バラバラなのがReplicaSet。
用途として、モニタリングツールやログ収集のPodに使う。

さっそく、試してみます。

sample-ds.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: sample-ds
spec:
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.12
          ports:
            - containerPort: 80
pi@raspi001:~/tmp $ k apply -f . --all --prune
daemonset.apps/sample-ds created
pi@raspi001:~/tmp $ k get all -o=wide
NAME                  READY   STATUS    RESTARTS   AGE   IP            NODE       NOMINATED NODE   READINESS GATES
pod/sample-ds-wxzbw   1/1     Running   0          60s   10.244.2.24   raspi003   <none>           <none>
pod/sample-ds-xjjtp   1/1     Running   0          60s   10.244.1.37   raspi002   <none>           <none>

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE    SELECTOR
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   6d1h   <none>

NAME                       DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE   CONTAINERS        IMAGES       SELECTOR
daemonset.apps/sample-ds   2         2         2       2            2           <none>          60s   nginx-container   nginx:1.12   app=sample-app

ReplicaSetと大きく違いはありません。
また、各ノードに対してpodが作られていることがわかります。

Deploymentと似ているアップデート戦略があり、OnDeleteとRollingUpdate(デフォルト)があります。前者は、podを明示的に削除した(k delete)際に更新する戦略です。DaemonSetは、死活監視やログ収集に使うので、手動でのタイミングが効くOnDeleteが好まれます。後者は、Deploymentと同じ動きで、即時更新していく戦略です。

ReplicaSetと似ているようで、機能的にはDeploymentに近い感じですね。ReplicaSetはpodが削除されたら複製されますけど、アップデートされません。DaemonSetはpodが削除されたら複製するし、アップデートもされます。試してみます。

sample-ds.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: sample-ds
spec:
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.13
          ports:
            - containerPort: 80

nginxのバージョンを1.12から1.13に変更しました。

pi@raspi001:~/tmp $ k apply -f . --all --prune
daemonset.apps/sample-ds configured
pi@raspi001:~/tmp $ k get all -o=wide
NAME                  READY   STATUS              RESTARTS   AGE   IP            NODE       NOMINATED NODE   READINESS GATES
pod/sample-ds-sx4mv   0/1     ContainerCreating   0          5s    <none>        raspi003   <none>           <none>
pod/sample-ds-xjjtp   1/1     Running             0          12m   10.244.1.37   raspi002   <none>           <none>

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE    SELECTOR
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   6d2h   <none>

NAME                       DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE   CONTAINERS        IMAGES       SELECTOR
daemonset.apps/sample-ds   2         2         1       1            1           <none>          12m   nginx-container   nginx:1.13   app=sample-app

applyしてみると、一台ずつupdateされています(containerCreating)。Deploymentと違うのは、最大pod数が1のために、一時的にpodが機能しなくなるタイミングが生まれます(超過分の設定不可)。

pi@raspi001:~/tmp $ k delete pod sample-ds-sx4mv
pod "sample-ds-sx4mv" deleted
pi@raspi001:~/tmp $ k get all -o=wide
NAME                  READY   STATUS              RESTARTS   AGE     IP            NODE       NOMINATED NODE   READINESS GATES
pod/sample-ds-hgvtv   0/1     ContainerCreating   0          6s      <none>        raspi003   <none>           <none>
pod/sample-ds-k8cfx   1/1     Running             0          4m38s   10.244.1.38   raspi002   <none>           <none>

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE    SELECTOR
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   6d2h   <none>

NAME                       DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE   CONTAINERS        IMAGES       SELECTOR
daemonset.apps/sample-ds   2         2         1       2            1           <none>          17m   nginx-container   nginx:1.13   app=sample-app

podを削除しても、セルフヒーリングで復活します。

StatefulSet

ステートレスなpodではなく、DBのようなステートフルなpod向けのリソース。
podを削除しても、データを永続的に保存する仕組みが存在。
動作自体は、replicaSetと似ている。

さっそく、試してみます。

sample-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: sample-statefulset
spec:
  serviceName: sample-statefulset
  replicas: 3
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.12
          ports:
            - containerPort: 80
          volumeMounts:
          - name: www
            mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 1G

mountPathで指定したマウントしたいパスを、volumeClaimTemplatesでマウントしてくれます。 どこに? :thinking:
Storageに関しては別で学習することにします。
ひとまず、applyします。

pi@raspi001:~/tmp $ k apply -f . --all --prune
daemonset.apps/sample-ds unchanged
statefulset.apps/sample-statefulset created
pi@raspi001:~/tmp $ k get pod
NAME                   READY   STATUS    RESTARTS   AGE
sample-ds-hgvtv        1/1     Running   0          54m
sample-ds-k8cfx        1/1     Running   0          58m
sample-statefulset-0   0/1     Pending   0          5m19s
pi@raspi001:~/tmp $ k describe pod sample-statefulset-0
...
Events:
  Type     Reason            Age                  From               Message
  ----     ------            ----                 ----               -------
  Warning  FailedScheduling  70s (x3 over 2m28s)  default-scheduler  pod has unbound immediate PersistentVolumeClaims (repeated 2 times)

おや、Pendingになってしまいました。 pod has unbound immediate PersistentVolumeClaims :thinking:

PersistentVolumeとPersistentVolumeClaims

PersistentVolume(永続的ボリューム)は、名前の通りで、データを永続的に保存しておく場所のリソースです。
マネージドサービスを利用すると、デフォルトでPresistentVolumeが用意されているそうです。
私の環境は、マネージドサービスではなく、自作環境であるので、PresistentVolumeを用意する必要があります。

PersistentVolumeClaims(永続的ボリューム要求)は、これも名前の通りで、「PresistentVolumeを使わせて」というリソースです。
このリソースで、PresistentVolumeのnameを指定し、applyすることで、初めてマウントができます。
例えば、PodからPersistentVolumeClaimsの名前を指定してあげると、そのPodはClaimしたPersistentVolumeをマウントすることができます。
volumeClaimTemplatesというのは、「わざわざPersistentVolumeClaimsを定義しなくてもテンプレートに沿って書けばClaimsできるよ」というものです。

で、何が問題だったの?

pod has unbound immediate PersistentVolumeClaimsのとおりで、「PersistentVolumeの要求をしたけど、Volume割当できなかったよ」とのことです。

PersistentVolume(pv)があるのか確認してみます。

pi@raspi001:~/tmp $ k get pv
No resources found.

たしかにないです。PersistentVolumeを用意しないといけないのですが、どうしましょう。
解決手段として考えたのは3点です。

  1. GCPやAWS,Azureのサービスを使う
  2. LocalVolumeを使う
  3. NFSを使う

types-of-persistent-volumes

1は、書いておいてなんですが、却下です。理由は、せっかくraspberryPiで構築したのでクラウドサービスを利用したくないからです。

2は、Kubernetes: Local Volumeの検証の参考にして試したのですが、 記事にも書いてあるとおり「Local Volumeは他のPodから共有で利用することができない」ため、statefulsetがreplica:1でなければ動きません。それはそれで動くので学習になり良いのですが、せっかくならreplicaの制限なしにしたいです(ReadWriteManyにしたい)。

3は、もう一台raspberryPiを用意して、それをNFSと見立ててPersistentVolumeにしてみる方法です。

3を進めようと思います。

NFS導入

サーバ設定

NFS用の新たなraspberryPiを用意します。設定手順はこちらを参考にしました。
その後の続きは下記です。

NFSのホスト名はnfspiとします。

~ $ slogin pi@nfspi.local
pi@nfspi:~ $ sudo apt-get install nfs-kernel-server
pi@nfspi:~ $ sudo vim /etc/exports
/home/data/ 192.168.3.0/255.255.255.0(rw,sync,no_subtree_check,fsid=0)

意味としては、「指定範囲のIPアドレスからのマウントを許可する」。オプションは、こちらを参照。

host ip
iMac 192.168.3.3
raspi001(master) 192.168.3.32
raspi002(worker) 192.168.3.33
raspi003(worker) 192.168.3.34
nfspi(NFS) 192.168.3.35
pi@nfspi:~ $ sudo mkdir -p /home/data
pi@nfspi:~ $ sudo chmod 755 /home/data
pi@nfspi:~ $ sudo chown pi:pi /home/data
pi@nfspi:~ $ sudo /etc/init.d/nfs-kernel-server restart
pi@nfspi:~ $ service rpcbind status
pi@nfspi:~ $ systemctl status nfs-server.service

正しく設定されたか、iMacから確認してみます。

~ $ mkdir share
~ $ sudo mount_nfs -P nfspi.local:/home/data ./share/
~ $ sudo umount share

OK

クライアント設定

各ノードに対して下記を実行します。

pi@raspi001:~ $ sudo apt-get install nfs-common

nfs-client導入

raspberryPi環境では、真っ白な状態なので、一からPersistentVolumeを用意する必要があります。それにはVolumeとなるStorageの型を用意する必要もあるのですが、Storage Classesを見る限り、NFS用の型は標準で存在しません。そこで、nfs-clientを使ってNFS用のStorageClassを作成します。

pi@raspi001:~ $ git clone https://github.com/kubernetes-incubator/external-storage.git && cd cd external-storage/nfs-client/
pi@raspi001:~/external-storage/nfs-client $ NS=$(kubectl config get-contexts|grep -e "^\*" |awk '{print $5}')
pi@raspi001:~/external-storage/nfs-client $ NAMESPACE=${NS:-default}
pi@raspi001:~/external-storage/nfs-client $ sed -i'' "s/namespace:.*/namespace: $NAMESPACE/g" ./deploy/rbac.yaml
pi@raspi001:~/external-storage/nfs-client $ k apply -f deploy/rbac.yaml

rbac.yamlにあるnamespaceを現在動かしている環境のnamespaceに置換して、applyしています。

pi@raspi001:~/external-storage/nfs-client $ k apply -f deploy/deployment-arm.yaml
pi@raspi001:~/external-storage/nfs-client $ k apply -f deploy/class.yaml

deployment-arm.yamlでは、NFSサーバのIPアドレス(192.168.3.35)とマウントパス(/home/data)を設定しました。
class.yamlが、今回欲していたNFSのstorageClass(managed-nfs-storage)になります。

※ raspberryPiのイメージはRaspbianを使っているので、arm用のdeployment-arm.yamlを使います。Wiki
これに随分とハマってしまいました... :cry:

pi@raspi001:~/external-storage/nfs-client $ k apply -f deploy/test-claim.yaml -f deploy/test-pod.yaml

試しにマウント先にファイルが作成できているのかテストしています。確認します。

nfspiに移動

pi@nfspi:~ $ ls /home/data

あれば成功です。あれば、下記で片付けます。

pi@raspi001:~/external-storage/nfs-client $ k delete -f deploy/test-pod.yaml -f deploy/test-claim.yaml

statefulsetをリトライ

以上で、StorageClassを用意できました。よって後は、PersistentVolume作って、PersistentVolumeClaim作って...となる予定でした。
しかし、nfs-clientには、dynamic provisioningという機能が備わっており、PersistentVolumeを作らなくても、PersistentVolumeClaimするだけで良くなります。この件については、storageを学習する際に書きます。

raspi001に移動して、sample-statefulset.yamlをもう一度applyします。
(storageClassName: managed-nfs-storageを追加, ReadWriteOnce→ReadWriteManyに変更)

sample-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: sample-statefulset
spec:
  serviceName: sample-statefulset
  replicas: 3
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.12
          ports:
            - containerPort: 80
          volumeMounts:
          - name: www
            mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes:
      - ReadWriteMany
      storageClassName: managed-nfs-storage
      resources:
        requests:
          storage: 1Gi
pi@raspi001:~/tmp $ k apply -f sample-statefulset.yaml

nfapiに移動して、あるか確認。

pi@nfspi:~ $ ls -la /home/data
total 20
drwxrwxrwx 5 pi     pi      4096 May  5 17:18 .
drwxr-xr-x 4 root   root    4096 May  4 15:50 ..
drwxrwxrwx 2 nobody nogroup 4096 May  5 17:17 default-www-sample-statefulset-0-pvc-5911505b-6f51-11e9-bb47-b827eb8ccd80
drwxrwxrwx 2 nobody nogroup 4096 May  5 17:18 default-www-sample-statefulset-1-pvc-5f2fd68e-6f51-11e9-bb47-b827eb8ccd80
drwxrwxrwx 2 nobody nogroup 4096 May  5 17:18 default-www-sample-statefulset-2-pvc-69bee568-6f51-11e9-bb47-b827eb8ccd80

ありました! マウントできています!

お片付け

--pruneでも良いのですが、下記のほうが使いやすかったです。

pi@raspi001:~/tmp $ k delete -f sample-ds.yaml -f sample-statefulset.yaml
pi@raspi001:~/tmp $ k delete pvc www-sample-statefulset-{0,1,2}

k get pvk get pvcを試して頂き、今回作ったリソースがありましたら削除お願いします。?

おわりに

StatefulSetを使える状態にするまでに記事が大きくなってしまいました。次回に詳しく学んでいこうと思います。笑
あと、nfs-clientを見て思ったのが、kubernetesのパッケージマネージャであるhelmを導入した方が、遥かに便利だと思いつつ、手動設定しました。。。
次回は、こちらです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む