zaki work log

作業ログやら生活ログやらなんやら

VS CodeのRemote Development SSHのサーバー側のプロセスを再起動する

Ctrl + Shift + p(winの場合)でコマンドパレットを出して「Remote-SSH: Kill VS Code Server on Host」を選択し、プロセスをkillする対象サーバーを指定する。

f:id:zaki-hmkc:20210126223848p:plain

実行するとどうなる

サーバー側のVS Codeプロセスが終了するため、一度VS Codeがリモートから切断される。
再度接続すればサーバー側のVS Codeプロセスが起動するため、また普通にRemote Developmentが使用可能。

f:id:zaki-hmkc:20210126223927p:plain

使いどころ

ユーザー情報を更新した場合

通常はこちら。

たとえばユーザーのグループ設定を変更(sudo usermod -aG docker $USERでグループ設定を更新した、とか)した場合、通常は新しくログインし直したシェルでなければ更新されたグループ情報は反映されない。

しかし、最近のVS CodeのRemote Development SSHは「VS Codeを終了」したり「リモートサーバーから切断」しても、サーバー上ではVS Code Serverプロセスが動き続けており、再度VS Codeから接続しても、動き続けていたVS Code Serverプロセスに再接続する形になるため、「再ログインすることで適用される設定」が反映されない。
平たく言うと切断してもscreentmuxのデタッチに似た状態になるため、再接続するとアタッチされて前の状態が継続される。

こういうときは「Kill VS Code Server on Host」を使うことで、古い情報を持っているServerプロセスを停止し、再接続することで新しい情報で接続できる。

接続先サーバーのOSを再起動してもよい。

原因は不明だがサーバーがハングってしまってとりあえず再起動したい

今日遭遇したのはこちら。

特定のPCのVS Codeで特定のホスト+ディレクトリに接続しようとするとなぜかVS Codeのウインドウが固まってしまい、そのままoomで死ぬという状況。

f:id:zaki-hmkc:20210126224345p:plain

f:id:zaki-hmkc:20210126224242p:plain

ちょっとどうにもならなかったので(OSリブートでもよかったんだけど)とりあえずVS Code Serverをkillしてみたら復旧した。


原因究明できなくて「とりあえず再起動したら治った」ってやつです…

[NetBox] AnsibleのNetBoxモジュールを使ってVMやIPアドレス情報を登録

以前REST APIを使った入力を試したけど、今回はAnsibleのNetboxモジュールを使った入力について。

docs.ansible.com

準備

pynetbox のインストール

NetBoxをAnsibleから操作する場合は、基本的に(全部チェックしたわけじゃないけどたぶん)全てpynetboxモジュールが必要。
pipでインストールする。

$ pip install pynetbox

無いとエラー。

TASK [create vm sample] *******************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ModuleNotFoundError: No module named 'pynetbox'
failed: [localhost] (item=k8s-master01) => changed=false 
  ansible_loop_var: item
  item: k8s-master01
  msg: Failed to import the required Python library (pynetbox) on cloud-dev's Python /home/zaki/src/ansible-sample/venv/a2.10/bin/python3. Please read the module documentation and install it in the appropriate location. If the required library is installed, but Ansible is using the wrong Python interpreter, please consult the documentation on ansible_python_interpreter

netbox.netboxコレクション

Ansible 2.10のフルセット(pip install ansibleでインストールした場合)はNetBoxのコレクションが含まれるので追加インストールは不要。
Ansible 2.10をansible-baseでインストールした場合、また、Ansible 2.9の場合は、netbox.netboxコレクションが追加で必要なのでansible-galaxyでインストールする。

ansible-galaxy collection install netbox.netbox

galaxy.ansible.com

Playbookサンプル

全部をチェックしたわけじゃないけど、VMの作成を試してみた限りでは「REST APIをAnsibleモジュールでラッピングしている」感じ。
なので、RESTを使った操作をチェックすればAnsibleモジュールの使い方もそのまま行ける。

ただ、RESTと違ってIDでなくVM名とかをそのまま使うことができてわかりやすい。
また、既にある場合はokとなって冪等な処理になり、VM名はそのままで例えばstatusを変更すれば「更新」動作になるので使い勝手は良い。

APIエンドポイントとトーク

各モジュールで共通して以下のパラメタが用意されている。

  • netbox_url
  • netbox_token

書いてみればすぐわかるけど、さらにdataといパラメタに、モジュールに対するパラメタ(つまりNetBoxへ操作を行いたいパラメタ)を指定していく。
基本的にNetBoxのweb上の画面とモジュールのドキュメントのExampleを見比べれば使い方は雰囲気でわかるはず。

VM作成

docs.ansible.com

    - name: create vm sample
      netbox.netbox.netbox_virtual_machine:
        netbox_url: "{{ netbox_url }}"
        netbox_token: "{{ netbox_token }}"
        data:
          name: "{{ item }}"
          cluster: vm network
      with_items:
        - k8s-master01

RESTだと必須だったclusterはドキュメントにはRequired設定されてないが、やっぱり必須。

clusterを指定しなかったらこの通り。

TASK [create vm sample] *******************************************************
failed: [localhost] (item=k8s-master01) => changed=false 
  ansible_loop_var: item
  item: k8s-master01
  msg: '{"cluster":["This field is required."]}'

clusterを指定(RESTの場合と異なり、clusterのIDでなくcluster名の文字列を指定)して実行すればこの通り。

(a2.10) [zaki@cloud-dev netbox (master)]$ ansible-playbook sample.yml -v
Using /home/zaki/src/ansible-sample/netbox/ansible.cfg as config file
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] **************************************************************

TASK [create vm sample] *******************************************************
changed: [localhost] => (item=k8s-master01) => changed=true 
  ansible_loop_var: item
  item: k8s-master01
  msg: virtual_machine k8s-master01 created
  virtual_machine:
    cluster: 3
    comments: ''
    config_context: {}
    created: '2021-01-13'
    custom_fields: {}
    disk: null
    id: 7
    last_updated: '2021-01-13T13:54:24.961868Z'
    local_context_data: null
    memory: null
    name: k8s-master01
    platform: null
    primary_ip: null
    primary_ip4: null
    primary_ip6: null
    role: null
    site: 1
    status: active
    tags: []
    tenant: null
    url: http://192.168.0.19:28080/api/virtualization/virtual-machines/7/
    vcpus: null

PLAY RECAP ********************************************************************
localhost                  : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

VM情報が登録されたのち、同じPlaybookで再実行するとokになり変化しない。

(a2.10) [zaki@cloud-dev netbox (master)]$ ansible-playbook sample.yml -v
Using /home/zaki/src/ansible-sample/netbox/ansible.cfg as config file
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] **************************************************************

TASK [create vm sample] *******************************************************
ok: [localhost] => (item=k8s-master01) => changed=false 
  ansible_loop_var: item
  item: k8s-master01
  msg: virtual_machine k8s-master01 already exists
  virtual_machine:
    cluster: 3
    comments: ''
    config_context: {}
    created: '2021-01-13'
    custom_fields: {}
    disk: null
    id: 7
    last_updated: '2021-01-13T13:54:24.961868Z'
    local_context_data: null
    memory: null
    name: k8s-master01
    platform: null
    primary_ip: null
    primary_ip4: null
    primary_ip6: null
    role: null
    site: 1
    status: active
    tags: []
    tenant: null
    url: http://192.168.0.19:28080/api/virtualization/virtual-machines/7/
    vcpus: null

PLAY RECAP ********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

(a2.10) [zaki@cloud-dev netbox (master)]$ 

以下についてもRESTのときとまったく同じ要領。
ただし前述の通り、IDでなく名前を指定する。

インベントリ変数やvarsなどで変数化しておけば、Primary設定のための「VM情報の更新」を行うnetbox_virtual_machineモジュール再実行も上記リンク先のサンプルのように簡単にできるはず。

Clusterの作成

せっかくなのでVM作成のパラメタのClusterも作成してみる。
Clusterの作成にはCluster Typeが必須なので、これもセットで作る。

作成に使用するパラメタをYAMLで変数定義しておく。

    cluster: vm network
    cluster_type:
      name: ESXi
      slug: esxi

これに対して、「Cluster Type作成 (netbox_cluster_type)」と「Cluster作成 (netbox_cluster)」が順に実行されるようにplaybookを書けばOK。

    - name: create cluster-type
      netbox.netbox.netbox_cluster_type:
        netbox_url: "{{ netbox_url }}"
        netbox_token: "{{ netbox_token }}"
        data:
          name: "{{ cluster_type.name }}"
          slug: "{{ cluster_type.slug }}"

    - name: create cluster
      netbox.netbox.netbox_cluster:
        netbox_url: "{{ netbox_url }}"
        netbox_token: "{{ netbox_token }}"
        data:
          name: "{{ cluster }}"
          cluster_type: "{{ cluster_type }}"

こんな感じ。

環境

NetBoxのバージョンはv2.10.3 (構築時と同じ)

Ansibleは以下の2パターンで確認。

Ansible 2.10

ansible 2.10.2
  config file = /home/zaki/src/ansible-sample/netbox/ansible.cfg
  configured module search path = ['/home/zaki/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/zaki/src/ansible-sample/venv/a2.10/lib64/python3.6/site-packages/ansible
  executable location = /home/zaki/src/ansible-sample/venv/a2.10/bin/ansible
  python version = 3.6.8 (default, Nov 16 2020, 16:55:22) [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)]
  • pynetbox: 5.3.0
  • netbox.netbox collections: 1.1.0

Ansible 2.9

ansible 2.9.16
  config file = /home/zaki/src/ansible-sample/netbox/ansible.cfg
  configured module search path = ['/home/zaki/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/zaki/src/ansible-sample/venv/2.9/lib64/python3.6/site-packages/ansible
  executable location = /home/zaki/src/ansible-sample/venv/2.9/bin/ansible
  python version = 3.6.8 (default, Nov 16 2020, 16:55:22) [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)]
  • pynetbox: 5.3.1
  • netbox.netbox collections: 2.0.0

あれ?いつのまにか2.0.0になってた。

サンプルコード

github.com


今回は「基本的に」が多いな。。触ってみたモジュールはだいたい同じ使い勝手だったけど、使い方が全く違うものが有るかもしれないし無いかもしれない(^^;


RESTを使った場合はこちら。

zaki-hmkc.hatenablog.com

[Ansible] 複数roleのhandlerとpost_tasks/pre_tasksの処理順序

特定のtaskに変更があったときだけ連動して動く処理を定義できるhandlerですが、処理の実行順序で思い違いしがちなところがあったので確認してみました。
(handlerを使ったroleがあるのを忘れて無邪気に次に処理されるroleを書き足したら期待しない実行順序になって処理がコケたというヤラカシがあったのでまとめ)

notify/handlerそのものについてはこちら参照。

zaki-hmkc.hatenablog.com

handlerを実装したrole

.
├── playbook.yml
└── roles
    ├── sample1
    │   ├── handlers
    │   │   └── main.yml
    │   └── tasks
    │       └── main.yml
    └── sample2
        └── tasks
            └── main.yml

roleは以下の通り。

  • sample1: taskshandlersを実装(taskの結果によってhandlerの中身が起動する)
  • sample2: tasksのみ

playbookは以下の通りで、sample1 -> sample2 の順に実行。

---
- hosts: localhost
  gather_facts: false

  roles:
  - sample1
  - sample2

ディレクトリ構造のイメージから、

  1. sample1のtask
  2. sample1のhandler
  3. sample2

の順序で実行されそう(roles/sample1で定義された全処理が終わったらroles/sample2)だけど、実際は、

  1. sample1のtask
  2. sample2のtask
  3. sample1のhandler

となり、「全rolesのtaskが処理されてからhandler」の順序で処理されます。

github.com

post_tasks

じゃあ「とあるrole/taskでhandlerを使いつつ、その処理後に通常のtask処理を行いたい」場合にどうすればよいかというと、post_tasksを使います。

  post_tasks:
  - name: this is post_tasks
    debug:
      msg: gochisousama desita.

  roles:
  - sample1
  - sample2

post_tasksに記述したtaskは、playbook内の記述順に関係なく、rolesの後に処理されます。

  1. roles/sample1のtask
  2. roles/sample2のtask
  3. roles/sample1のhandler
  4. post_tasksのtask

※ ↑のplaybookは処理順が決まってることを確認するためにpost_tasksを最初に書いているけど、実際は最後に書いた方が直観的に処理順序が分かりやすい。

pre_tasks

postがあるならpreもある。

  post_tasks:
  - name: this is post_tasks
    debug:
      msg: gochisousama desita.

  roles:
  - sample1
  - sample2

  pre_tasks:
  - name: this is pre_tasks
    debug:
      msg: itadakimasu

post_tasksと同じように、playbook内の記述順に関係なく、rolesの前に処理されます。

  1. pre_tasksのtask
  2. roles/sample1のtask
  3. roles/sample2のtask
  4. roles/sample1のhandler
  5. post_tasksのtask

※ ↑のplaybookは処理順が決まってることを確認するためにpost_tasksの例同様pre_tasksを最後に書いているけど(ry

post_tasks/pre_tasksでhandler使用

  post_tasks:
  - name: this is post_tasks
    debug:
      msg: gochisousama desita.
  - name: exec at post_task
    command: ls
    notify: post_notify

  roles:
  - sample1
  - sample2

  handlers:
  - name: pre_notify
    debug:
      msg: handler at pre_notify
    listen: pre_notify
  - name: post_notify
    debug:
      msg: handler at post_notify
    listen: post_notify

  post_tasks:
  - name: this is post_tasks
    debug:
      msg: gochisousama desita.
  - name: exec at pre_task
    command: ls
    notify: pre_notify

これはrolesのディレクトリ構造の勘違いと違って、

  1. pre_tasksのtask
  2. pre_tasksのhandler (ここでpre_tasksの全処理が完了)
  3. rolesのsample1, sample2, rolesのhandler (ここでrolesの全処理が完了)
  4. post_tasksのtask
  5. post_tasksのhandler

「一連のtaskの中で複数回notifyされてもhandlerのtaskが処理されるのは1回」が基本動作だけど、post_taskspre_tasksのhandlerはそれぞれpost_tasksのhandler、pre_tasksのhandlerとして独立して1回ずつ処理されます。

post_tasks/pre_tasksのnotify先が同じtaskの場合

これは、handlerでnotifyで通知されたtaskはpost_tasks/pre_tasksそれぞれで処理されます。
tasksを追加しても同様に個別に処理されます。 「post_tasks内で複数回notifyされても処理は1回」だけど「post_tasks,pre_tasksそれぞれでnotifyされた場合は、それぞれで処理される」(テキストだと説明難しいな。。)

  post_tasks:
  - name: exec at post_task
    command: ls
    notify: post_notify

  pre_tasks:
  - name: exec at pre_task
    command: ls
    notify: pre_notify

  tasks:
  - name: exec at task
    command: ls
    notify: task_notify

  handlers:
  - name: handler_at_task
    debug:
      msg: handler at task
    listen: task_notify
  - name: pre_notify
    debug:
      msg: handler at pre_notify
    listen: pre_notify
  - name: post_notify
    debug:
      msg: handler at post_notify
    listen: post_notify
  - name: all notify task
    debug:
      msg: curry!
    listen:
    - task_notify
    - pre_notify
    - post_notify

handlersセクションがちょっとゴチャゴチャしてるけど、この場合の処理順序は以下の通り。

  1. pre_tasksのtask
  2. pre_tasksのhandlerでpre_notify
  3. pre_tasksのhandlerでall notify task
  4. tasks
  5. tasksのhandlerでhandler_at_task
  6. tasksのhandlerでall notify task
  7. post_tasksのtask
  8. post_tasksのhandlerでpre_notify
  9. post_tasksのhandlerでall notify task

(参考) tasksとrolesの併用

例題でよく出てくると思うけど、playの中でtasksrolesを両方定義した場合の処理順序。

  tasks:
  - name: this task by "tasks" in playbook
    debug:
      msg: hello
  - name: exec at task
    command: ls
    notify: task_notify

  roles:
  - sample1
  - sample2

  handlers:
  - name: handler_at_task
    debug:
      msg: handler at task
    listen: task_notify

この場合は、記述順に関係なく、

  1. rolesの全てのtasks
  2. playの全てのtasks
  3. handlers
    • rolesのhandlers
    • playのhandlers

の順序。

github.com

(参考) tasks/roles/pre_tasks/post_tasksの併用

roles+tasksにさらにpre_tasks/post_tasksを組み込むとroles/tasksの前後に処理されます。

  1. pre_tasksの全処理
  2. rolesの全tasks
  3. tasksの全tasks
  4. roles/tasksのhandlers
  5. post_tasksの全処理

github.com


処理順について書かれたドキュメント

docs.ansible.com

  • Any pre_tasks defined in the play.
  • Any handlers triggered by pre_tasks.
  • Each role listed in roles:, in the order listed. Any role dependencies defined in the role’s meta/main.yml run first, subject to tag filtering and conditionals. See Using role dependencies for more details.
  • Any tasks defined in the play.
  • Any handlers triggered by the roles or tasks.
  • Any post_tasks defined in the play.
  • Any handlers triggered by post_tasks.

https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html


docs.ansible.com

  • handlers notified within pre_tasks, tasks, and post_tasks sections are automatically flushed at the end of section where they were notified.
  • handlers notified within roles section are automatically flushed at the end of tasks section, but before any tasks handlers.
  • handlers are play scoped and as such can be used outside of the role they are defined in.

https://docs.ansible.com/ansible/latest/user_guide/playbooks_handlers.html

2.9だとこの記述は以下のURL。

docs.ansible.com

pre_tasks(task -> handler) -> roles -> tasks -> handler(roles & tasks) -> post_tasks(task -> handler) の順序は明記されてるけど、rolesのhandlerとtasksのhandlerは特に明記されてない気がする。
(実際に動かしてみると、rolesのhandler -> tasksのhandlerの順になった)

処理順まとめ

  1. pre_tasksのtask
  2. pre_tasksによるhandler
  3. 全rolesのtask
  4. 全tasks
  5. rolesによる全handler
  6. tasksによる全handler
  7. post_tasksのtask
  8. post_tasksによるhandler

確認環境

(a2.10) [zaki@cloud-dev pre_post_tasks (master)]$ ansible-playbook --version
ansible-playbook 2.10.2
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/zaki/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/zaki/src/ansible-sample/venv/a2.10/lib64/python3.6/site-packages/ansible
  executable location = /home/zaki/src/ansible-sample/venv/a2.10/bin/ansible-playbook
  python version = 3.6.8 (default, Nov 16 2020, 16:55:22) [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)]

微妙に古いね…

[Ansible] acme_certificateモジュールでLet's Encryptから証明書を発行しApacheでHTTPSサーバー構築

httpdパッケージでApacheをインストール・HTTPS対応し、Let's Encryptの証明書の発行と設定までAnsibleで設定、できるのかな?と思って試したらうまくいった。

Let's Encryptで証明書を作成するには、ACMEプロトコルを利用し、このACMEプロトコルを操作するためのacme_certificateモジュールがAnsibleには(バージョン2.10ではcommunity.cryptoコレクションに)用意されている。

docs.ansible.com

証明書発行部分は外部からのHTTPアクセスに対応できれば良いので、特にApacheである必要はなくNginxでも何でもよい。

環境

(a2.10) [zaki@cloud-dev acme (master)]$ cat /etc/centos-release
CentOS Linux release 7.9.2009 (Core)
(a2.10) [zaki@cloud-dev acme (master)]$ ansible --version
ansible 2.10.2
config file = /home/zaki/src/ansible-sample/acme/ansible.cfg
configured module search path = ['/home/zaki/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /home/zaki/src/ansible-sample/venv/a2.10/lib64/python3.6/site-packages/ansible
executable location = /home/zaki/src/ansible-sample/venv/a2.10/bin/ansible
python version = 3.6.8 (default, Nov 16 2020, 16:55:22) [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)]
  • ターゲットノード(webサーバー)
[root@instance-20200127-2223 ~]# cat /etc/oracle-release 
Oracle Linux Server release 7.9

ドメインは別途設定。

ターゲットノードはOracle CloudのAlways Free枠のインスタンス
グローバルIPとAレコードを設定したFQDNがあれば仕組み的に特に環境は問わない。
外部からの80/TCP443/TCPは全許可。
(証明書発行が済んだら制限して良い)

内容はRPM系のLinux OSになってるので、APT系の場合はコマンドやパスが異なる場合は読み替えること。

playbook

実際に動作確認したものをGitHubにあげていますので急いでる方はこちらをどうぞ。

github.com

流れ

基本的な流れはacme_certificateモジュールのExamplesを見れば良かった。

httpdのインストールとHTTPS対応

Yum系のLinuxの場合は、mod_sslパッケージを入れればlocalhost用証明書付きで/etc/httpd/conf.d/ssl.conf/etc/httpd/conf.modules.d/00-ssl.confも作成される。

証明書がダミーという点を除いて、起動すれば443/TCPでListenする。

なおapache2_moduleモジュールも存在するが、このモジュールで必要なa2enmodAPT系でapache2をインストールすると付属するが、Yumhttpdには含まれてなかった。

Let's Encryptで証明書発行

ExamplesとPlaybookを見た方が早いですが、大まかな流れは以下の通り。

  1. Let's Encrypt用のアカウントキーを作成
  2. サーバー証明書秘密鍵を作成
  3. サーバー証明書秘密鍵から証明書署名要求(CSR)を作成
  4. 作成したアカウントキーとCSRでLet's EncryptにACMEのチャレンジ要求
  5. チャレンジ要求のレスポンスの内容でトークンファイルをwebサーバー上に作成
    • 62-71行目
    • レスポンスはregisterを使えば簡単に参照できる
    • デフォルトのHTTP-01チャレンジに対するトークンファイルは http://<YOUR_DOMAIN>/.well-known/acme-challenge/<TOKEN> に作成する
    • 平たく言うと、Let's Encryptが「このファイルをあなたのサーバー上で用意してね。後からそれが設置されてるか見に行く(HTTPアクセスする)よ。設定されてたら証明書作ってあげるね」と言っているので、その通りにする
  6. チャレンジの検証を要求し、証明書を取得する
    • 73-81行目
    • パラメタはチャレンジ要求時とほぼ同じだがdataに最初のレスポンス内容を追加している
  7. webサーバーで鍵のパスを設定する
    • 83-92行目
    • 証明書(crtファイル)が作成されたので、Apacheの設定のSSLCertificateFileSSLCertificateKeyFileの設定で証明書ファイルのパスを設定する

要点

f:id:zaki-hmkc:20210118073427p:plain

f:id:zaki-hmkc:20210118073437p:plain

[NetBox] 「VMを登録してIPアドレス割り当て」をREST APIでお試し

NetBoxのREST APIをざっくりと使って「VMを登録してIPアドレスを割り当てる」をやってみる。
APIについては、NetBoxの画面下部の{} APIのリンク先を開くと、Swaggerが表示されるので、エンドポイントとHTTPリクエストメソッド一覧や必要なパラメタ、レスポンス等を確認できる。
NetBoxにおけるSwaggerの操作はこの辺を参照。

f:id:zaki-hmkc:20210113090328p:plain

netbox.readthedocs.io


環境

本環境ではNetBoxは http://192.168.0.19:28080/ で稼働している。
下記参照。

zaki-hmkc.hatenablog.com

また、記事内のHTTPアクセスの内容は基本的にVS CodeのREST Client拡張を使って確認したときのもの。
curlを使う場合はヘッダ指定の-Hや、リクエストBodyを指定するための-dを使用する。

qiita.com

トーク

APIを実行した操作にはトークンが必要。 Docker ComposeでデプロイしたNetBoxは、デフォルトでは 0123456789abcdef0123456789abcdef01234567 になっている。

トークンの値はAdministration画面のTokensから確認できる。

f:id:zaki-hmkc:20210113090553p:plain

HTTPアクセス時には Authorization: Token ${TOKEN値} という書式のリクエストヘッダを付与すれば良い。

netbox.readthedocs.io

Cluster IDを確認

VM情報の登録にはCluster IDが必須項目なので、登録済みCluster IDを確認する。
エンドポイントは /virtualization/clusters/ を使用。

全Clusterを取得するには、特にQuery String無し。

GET http://192.168.0.19:28080/api/virtualization/clusters/
Authorization: Token {{ token }}

Cluster名を指定してフィルタするには以下の通り。(スペースは+エスケープ)

GET http://192.168.0.19:28080/api/virtualization/clusters/?name=vm+network
Authorization: Token {{ token }}

レスポンスは以下の通りで、これから使いたい"vm network"Clusterはid=3`であることが分かる。

HTTP/1.1 200 OK
Server: nginx
Date: Tue, 12 Jan 2021 22:40:09 GMT
Content-Type: application/json
Content-Length: 537
Connection: close
Vary: Accept, Cookie, Origin
Allow: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
API-Version: 2.10
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
X-Frame-Options: SAMEORIGIN
P3P: CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"

{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 3,
      "url": "http://192.168.0.19:28080/api/virtualization/clusters/3/",
      "name": "vm network",
      "type": {
        "id": 2,
        "url": "http://192.168.0.19:28080/api/virtualization/cluster-types/2/",
        "name": "ESXi",
        "slug": "esxi"
      },
      "group": null,
      "tenant": null,
      "site": {
        "id": 1,
        "url": "http://192.168.0.19:28080/api/dcim/sites/1/",
        "name": "自宅",
        "slug": "home"
      },
      "comments": "",
      "tags": [],
      "custom_fields": {},
      "created": "2021-01-11",
      "last_updated": "2021-01-11T06:02:43.351913Z",
      "device_count": 0,
      "virtualmachine_count": 4
    }
  ]
}

VM情報の登録

使用するCluster IDが3わかったのでVMを登録する。
エンドポイントは /virtualization/virtual-machines/ を使用。

必須パラメタはnameclusterになっているので、ひとまずそれだけ指定する。
ここでは client-dev というVMを登録する。

POST http://192.168.0.19:28080/api/virtualization/virtual-machines/
Authorization: Token {{ token }}
Content-Type: application/json

{
    "name": "client-dev",
    "cluster": 3
}

レスポンスは以下。

HTTP/1.1 201 Created
Server: nginx
Date: Tue, 12 Jan 2021 22:45:46 GMT
Content-Type: application/json
Content-Length: 642
Connection: close
Location: http://192.168.0.19:28080/api/virtualization/virtual-machines/6/
Vary: Accept, Cookie, Origin
Allow: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
API-Version: 2.10
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
X-Frame-Options: SAMEORIGIN
P3P: CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"

{
  "id": 6,
  "url": "http://192.168.0.19:28080/api/virtualization/virtual-machines/6/",
  "name": "client-dev",
  "status": {
    "value": "active",
    "label": "Active"
  },
  "site": {
    "id": 1,
    "url": "http://192.168.0.19:28080/api/dcim/sites/1/",
    "name": "自宅",
    "slug": "home"
  },
  "cluster": {
    "id": 3,
    "url": "http://192.168.0.19:28080/api/virtualization/clusters/3/",
    "name": "vm network"
  },
  "role": null,
  "tenant": null,
  "platform": null,
  "primary_ip": null,
  "primary_ip4": null,
  "primary_ip6": null,
  "vcpus": null,
  "memory": null,
  "disk": null,
  "comments": "",
  "local_context_data": null,
  "tags": [],
  "custom_fields": {},
  "config_context": {},
  "created": "2021-01-12",
  "last_updated": "2021-01-12T22:45:46.414175Z"
}

画面で見てもVMが登録されたことを確認できる。

f:id:zaki-hmkc:20210113090842p:plain

インタフェース作成

IPアドレスの設定のためにはインタフェースを作成する。
上記のVM作成時のレスポンスでVMのIDは6だとわかっているので、それを使用する。
エンドポイントは /virtualization/interfaces/ を使用する。

必須項目は virtual_machine(integer)と、name(string)の二つ

POST http://192.168.0.19:28080/api/virtualization/interfaces/
Authorization: Token {{ token }}
Content-Type: application/json

{
    "virtual_machine": 6,
    "name": "ens192"
}

レスポンスは以下の通り。

HTTP/1.1 201 Created
Server: nginx
Date: Tue, 12 Jan 2021 22:50:55 GMT
Content-Type: application/json
Content-Length: 334
Connection: close
Location: http://192.168.0.19:28080/api/virtualization/interfaces/8/
Vary: Accept, Cookie, Origin
Allow: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
API-Version: 2.10
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
X-Frame-Options: SAMEORIGIN
P3P: CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"

{
  "id": 8,
  "url": "http://192.168.0.19:28080/api/virtualization/interfaces/8/",
  "virtual_machine": {
    "id": 6,
    "url": "http://192.168.0.19:28080/api/virtualization/virtual-machines/6/",
    "name": "client-dev"
  },
  "name": "ens192",
  "enabled": true,
  "mtu": null,
  "mac_address": null,
  "description": "",
  "mode": null,
  "untagged_vlan": null,
  "tagged_vlans": [],
  "tags": []
}

VMの詳細画面を開くと、インタフェースが追加されたことを確認できる。

f:id:zaki-hmkc:20210113091026p:plain

IPアドレスの作成とインタフェースへのアサイ

作成したインタフェースのIDは上記で8になっている。
IPアドレスを作成し、このインタフェースへ割り当てを行う。 (割り当ては行わずにIPアドレス作成のみも可能)

エンドポイントは /ipam/ip-addresses/ を使用。

必須項目は address(string) で、インタフェースへの割り当てにはオプションの assigned_object_type(string) と assigned_object_id(integer) を指定する。
あとAPIドキュメントだと nat_outside にも必須(required)のマークが付いているが、無くても動く。

assigned_object_typeに何を指定するかドキュメントでちょっと確認できなかったが、「作成済みIPアドレス一覧を取得(/ipam/ip-addresses/にGET)」すると、以下のようになっていたのでそれを指定。

オブジェクト種別 assigned_object_typeの値
Device "dcim.interface"
Virtual Machine "virtualization.vminterface"

ということでリクエストは以下の通り。

POST http://192.168.0.19:28080/api/ipam/ip-addresses/
Authorization: Token {{ token }}
Content-Type: application/json

{
    "address": "192.168.0.17/24",
    "assigned_object_type": "virtualization.vminterface",
    "assigned_object_id": 8
}

レスポンスは以下の通り。

HTTP/1.1 201 Created
Server: nginx
Date: Tue, 12 Jan 2021 23:00:55 GMT
Content-Type: application/json
Content-Length: 685
Connection: close
Location: http://192.168.0.19:28080/api/ipam/ip-addresses/48/
Vary: Accept, Cookie, Origin
Allow: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
API-Version: 2.10
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
X-Frame-Options: SAMEORIGIN
P3P: CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"

{
  "id": 48,
  "url": "http://192.168.0.19:28080/api/ipam/ip-addresses/48/",
  "family": {
    "value": 4,
    "label": "IPv4"
  },
  "address": "192.168.0.17/24",
  "vrf": null,
  "tenant": null,
  "status": {
    "value": "active",
    "label": "Active"
  },
  "role": null,
  "assigned_object_type": "virtualization.vminterface",
  "assigned_object_id": 8,
  "assigned_object": {
    "id": 8,
    "url": "http://192.168.0.19:28080/api/virtualization/interfaces/8/",
    "virtual_machine": {
      "id": 6,
      "url": "http://192.168.0.19:28080/api/virtualization/virtual-machines/6/",
      "name": "client-dev"
    },
    "name": "ens192"
  },
  "nat_inside": null,
  "nat_outside": null,
  "dns_name": "",
  "description": "",
  "tags": [],
  "custom_fields": {},
  "created": "2021-01-12",
  "last_updated": "2021-01-12T23:00:55.356643Z"
}

これでインタフェースにIPアドレスが割り当て状態となる。

f:id:zaki-hmkc:20210113091135p:plain

IPアドレスの詳細はこの通り。

f:id:zaki-hmkc:20210113091235p:plain

Primary IPに設定

f:id:zaki-hmkc:20210113091336p:plain

IPアドレスの割り当てはこれで完了したが、「Primary IPアドレス設定」になっていないため、このままだと一覧で表示されない。
Primary IPアドレスVMの設定で行う。

エンドポイントは /virtualization/virtual-machines/{id}/ を使用。

ここまでの作成手順で、VMのIDは6、作ったIPアドレスのID(IPじゃないよ)は48なので、リクエストは以下の通り。
(ついでなのでroleも指定)

PUT http://192.168.0.19:28080/api/virtualization/virtual-machines/6/
Authorization: Token {{ token }}
Content-Type: application/json

{
    "name": "client-dev",
    "cluster": 3,
    "primary_ip4": 48,
    "role": 2
}

レスポンスは以下の通り。

HTTP/1.1 200 OK
Server: nginx
Date: Tue, 12 Jan 2021 23:42:42 GMT
Content-Type: application/json
Content-Length: 945
Connection: close
Vary: Accept, Cookie, Origin
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
API-Version: 2.10
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
X-Frame-Options: SAMEORIGIN
P3P: CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"

{
  "id": 6,
  "url": "http://192.168.0.19:28080/api/virtualization/virtual-machines/6/",
  "name": "client-dev",
  "status": {
    "value": "active",
    "label": "Active"
  },
  "site": {
    "id": 1,
    "url": "http://192.168.0.19:28080/api/dcim/sites/1/",
    "name": "自宅",
    "slug": "home"
  },
  "cluster": {
    "id": 3,
    "url": "http://192.168.0.19:28080/api/virtualization/clusters/3/",
    "name": "vm network"
  },
  "role": {
    "id": 2,
    "url": "http://192.168.0.19:28080/api/dcim/device-roles/2/",
    "name": "client",
    "slug": "client"
  },
  "tenant": null,
  "platform": null,
  "primary_ip": {
    "id": 48,
    "url": "http://192.168.0.19:28080/api/ipam/ip-addresses/48/",
    "family": 4,
    "address": "192.168.0.17/24"
  },
  "primary_ip4": {
    "id": 48,
    "url": "http://192.168.0.19:28080/api/ipam/ip-addresses/48/",
    "family": 4,
    "address": "192.168.0.17/24"
  },
  "primary_ip6": null,
  "vcpus": null,
  "memory": null,
  "disk": null,
  "comments": "",
  "local_context_data": null,
  "tags": [],
  "custom_fields": {},
  "config_context": {},
  "created": "2021-01-12",
  "last_updated": "2021-01-12T23:42:42.492869Z"
}

Primary IPアドレスが設定できました。

f:id:zaki-hmkc:20210113091421p:plain


UpdateせずにCreateだけでPrimary IPを設定する方法はちょっとわからなかった。
とはいえ、手動でなく自動処理するのであれば、この点はそこまでこだわらなくても良い気もする。

  • Primary IPの設定はVMの設定項目
  • ただしIPアドレスがインタフェースへ設定済みでないとエラー
  • IPアドレスは単体で作成できるがインタフェース割り当てはインタフェースを作成しておく必要がある
  • インタフェースを作成するにはVMを作成しておく必要がある

Docker版NetBoxを導入してIPアドレス管理台帳.xlsxに別れを告げる

オンプレ環境だと必ずと言っていいほど存在する「エクセルのIPアドレス管理台帳」、なんやかやで私も自宅の環境でもスプレッドシート(Excelではない笑)に記入しつつ運用してました。
で、業務で最近NetBoxというアドレス管理などを行うIPAMツールを使うことが増えたので、勉強がてら自宅にも導入してみました。

github.com

netbox.readthedocs.io

github.com

NetBoxって何

めちゃくちゃ雑に言うと「IPアドレス管理台帳のwebアプリ版」と解釈してます。
詳しくは公式サイトと、IPAM・DCIMなどで検索すると詳しい情報が見つかると思います。
元々はDigitalOcean社のネットワークエンジニアリングチームが開発したもので、PythonDjangoで実装されています。

インストール

環境

以下の環境にデプロイ。

[zaki@manager ~]$ cat /etc/centos-release
CentOS Linux release 7.9.2009 (Core)
[zaki@manager ~]$ docker -v
Docker version 20.10.2, build 2291f61
[zaki@manager ~]$ docker-compose -v
docker-compose version 1.27.4, build 40524192

また、NetBoxのバージョンは v2.10.3 となっている。

NetBoxインストール

Dockerのコンテナイメージが公開されており、Quickstartに沿ってDocker Composeを使えばサクッとデプロイできる。動作も軽量。

[zaki@manager local]$ git clone -b release https://github.com/netbox-community/netbox-docker.git
Cloning into 'netbox-docker'...
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 2417 (delta 0), reused 0 (delta 0), pack-reused 2412
Receiving objects: 100% (2417/2417), 624.70 KiB | 588.00 KiB/s, done.
Resolving deltas: 100% (1371/1371), done.
[zaki@manager local]$ cd netbox-docker/

ここで、ドキュメントだと「ホストOSでは8000/TCPでListenする設定」になるが、8000は別に使ってるので28080/TCPに変更。(数に深い意味はない)

$ tee docker-compose.override.yml <<EOF
version: '3.4'
services:
  nginx:
    ports:
      - 28080:8080
EOF

イメージをpull

[zaki@manager netbox-docker]$ docker-compose pull
Pulling redis         ... done
Pulling postgres      ... done
Pulling netbox-worker ... done
Pulling redis-cache   ... done
Pulling netbox        ... done
Pulling nginx         ... done

デプロイ

[zaki@manager netbox-docker]$ docker-compose up -d
Creating network "netbox-docker_default" with the default driver
Creating volume "netbox-docker_netbox-static-files" with local driver
Creating volume "netbox-docker_netbox-nginx-config" with local driver
Creating volume "netbox-docker_netbox-media-files" with local driver
Creating volume "netbox-docker_netbox-postgres-data" with local driver
Creating volume "netbox-docker_netbox-redis-data" with local driver
Creating netbox-docker_redis-cache_1   ... done
Creating netbox-docker_redis_1       ... done
Creating netbox-docker_postgres_1    ... done
Creating netbox-docker_netbox-worker_1 ... done
Creating netbox-docker_netbox_1        ... done
Creating netbox-docker_nginx_1         ... done
[zaki@manager netbox-docker]$ docker-compose ps
            Name                           Command               State                            Ports                         
--------------------------------------------------------------------------------------------------------------------------------
netbox-docker_netbox-worker_1   python3 /opt/netbox/netbox ...   Up                                                             
netbox-docker_netbox_1          /opt/netbox/docker-entrypo ...   Up                                                             
netbox-docker_nginx_1           /docker-entrypoint.sh ngin ...   Up      80/tcp, 0.0.0.0:28080->8080/tcp,0.0.0.0:49153->8080/tcp
netbox-docker_postgres_1        docker-entrypoint.sh postgres    Up      5432/tcp                                               
netbox-docker_redis-cache_1     docker-entrypoint.sh sh -c ...   Up      6379/tcp                                               
netbox-docker_redis_1           docker-entrypoint.sh sh -c ...   Up      6379/tcp    

割とすぐUpにはなるが、初回は内部ではDBの初期データ構築が行われている。

[zaki@manager netbox-docker]$ docker-compose logs -f
Attaching to netbox-docker_nginx_1, netbox-docker_netbox_1, netbox-docker_netbox-worker_1, netbox-docker_redis_1, netbox-docker_postgres_1, netbox-docker_redis-cache_1
netbox_1         | 🧬 loaded config '/etc/netbox/config/configuration.py'
netbox_1         | 🧬 loaded config '/etc/netbox/config/configuration.py'
netbox_1         | 🧬 loaded config '/etc/netbox/config/extra.py'
netbox_1         | Operations to perform:
netbox_1         |   Apply all migrations: admin, auth, circuits, contenttypes, dcim, extras, ipam, secrets, sessions, taggit, tenancy, users, virtualization
netbox_1         | Running migrations:

:
:

ログにnetboxのコンテナでListeneing at ... と出ていればだいたい大丈夫。

netbox_1         | 🧬 loaded config '/etc/netbox/config/configuration.py'
netbox_1         | 🧬 loaded config '/etc/netbox/config/configuration.py'
netbox_1         | 🧬 loaded config '/etc/netbox/config/extra.py'
netbox_1         | 
netbox_1         | 961 static files copied to '/opt/netbox/netbox/static'.
netbox_1         | ✅ Initialisation is done.
netbox_1         | [2021-01-09 09:04:31 +0000] [1] [INFO] Starting gunicorn 20.0.4
netbox_1         | [2021-01-09 09:04:31 +0000] [1] [INFO] Listening at: http://0.0.0.0:8001 (1)
netbox_1         | [2021-01-09 09:04:31 +0000] [1] [INFO] Using worker: sync
netbox_1         | [2021-01-09 09:04:31 +0000] [20] [INFO] Booting worker with pid: 20
netbox_1         | [2021-01-09 09:04:31 +0000] [21] [INFO] Booting worker with pid: 21
netbox_1         | [2021-01-09 09:04:31 +0000] [22] [INFO] Booting worker with pid: 22

webブラウザでHTTPアクセスすればNetBoxの画面にアクセスできる。

f:id:zaki-hmkc:20210111161156p:plain

コンテナは以下のものが動いているが、起動してしまえば(初回の初期処理が終われば)負荷はあまりない。

  • Nginx
  • NetBox
  • NetBox Worker
  • Redis
  • PostgreSQL
  • Redis Cache

ログイン

画面右上の「Log in」から。 Docker Composeで構築した場合はユーザー名・パスワードともにadminとなっている。

管理方針 (仮)

軽くさわってみた感じだと、自宅の環境(NW機器が特に無く、Linux/Windows等のサーバー・クライアントPCばかり)の管理としてはNUCなどの物理機器の情報はDCIM -> Devicesに、VMVirtualization -> Virtual Machinesにホスト情報を登録すると良さそうだった。
(あくまで使い方の一つで、私はこうやって使ってみようというもの。使ってるともっと別の使い方が良いと思えば変更するかも。)

ただし、ホスト情報の登録には選択式のパラメタがいくつか必須で、そのパラメタの選択肢を予め作成しておく必要がある。

Devices

バイス情報を登録するには、以下の必須項目があるので、まずこれらを作成しておく。

  • Site
  • Device Role
  • Device Type
  • Manufactures (Device Typeの作成に必要)

あと、必須ではないが、Rackなんかは自宅ラック勢には設定すると楽しいかもしれない。
自宅ラック勢でなくても、マシンの棚があってサーバー・ルーター・モデム・外付けHDDなどの機器がそれぞれ何段目にあるかを登録したりもできるかも?(未確認)

あとTag設定も可能で、これは事前に使いたいタグ情報を作成しておけば、複数のタグを設定できる。

Sites

f:id:zaki-hmkc:20210111161709p:plain

[Organization -> Sites]から。
画面上部のメニューの+か、Sites画面の右上の+ Addから項目を作成する。

名前の通り場所を登録する。例えば「自宅」とか。

Slugには、URLにも使われるユニークな識別子を入力する。
APIなどでも使用するので、英数字とハイフン程度にしておくのが無難。(たぶん)
名称にスペースを含むとハイフンになるなど、ASCII文字であれば自動で反映される。

項目
Site / Name 自宅
Site / Slug home
Site / Status Active

こんな感じ。

f:id:zaki-hmkc:20210111161908p:plain

Device Roles

名前の通り、デバイス(今回はホスト)の役割を定義。
サイトと同じ要領で、「Devices」の「Device Roles」から、「+ Add」押下で作成画面になる。

本来はNW機器の役割の登録に使用するっぽいが、自宅の環境はISPルーターと家電店で買ってきたスイッチくらいしかなく、メインの管理対象はサーバーとクライアントPCのため、あまり細かく分けずに「サーバー」「クライアント」「k8sノード」「ネットワーク」「テンポラリ」みたいに設定してみる。
なお、デバイスに割り当てられるロールは一つのみ。

f:id:zaki-hmkc:20210111162112p:plain

VM Roleのチェックについては、DevicesだけでなくVirtual MachinesのRoleの設定で割り当て対象とするかどうか、というもの。
(DevicesではRoleの設定は必須だが、Virtual MachinesではRoleの設定は任意)

Manufacturers

Devicesでデバイス登録するのに必須なのはDevice Typesだが、Device Typesの項目を作るのに必須なのがManufacturersになっている。
バイス登録時にDevice Typesを選択すると、自動的にManufacturersが決定されるようになるため、親子関係に当たる。

なので、初めはmanufacturesbare metal,VMを設定してDevice TypesLInux / Windows / ...なんて設定してみたけど構造的にイケてなかったのとVM登録用のVirtual Machinesという項目もあったので、結局名前の通り「Manufacturersには物理デバイス・PCのメーカー」と「Device Typesにはデバイス・PCのモデル名」という使い方にしてみた。

f:id:zaki-hmkc:20210111162158p:plain

Device Types

Manufacturersに機器メーカーを登録したら、Device Typesに機器のモデル名を登録。
元々データセンターのラックマウントを想定した設定内容のためHeight (U)という項目はあるが、一般のご家庭なので使用しないが必須項目のが目デフォルトの1で。

f:id:zaki-hmkc:20210111162235p:plain

Deviceの登録

  • Site
  • Device Role
  • Device Type

以上の必須項目の準備ができたのでデバイスを登録する。
名前はホスト名。
前述の通り、Manufacturerは空欄のままでも、「Create」したときに選択していたDevice typeから自動で設定される。

Virtualizationのところで選択できるCluster GroupあるいはClusterは、Virtual Machinesでは必須の項目だが、Devicesでは任意。

f:id:zaki-hmkc:20210111162349p:plain

画面の通り、一覧ではTypeのフィールドが「Manufacturer + Device Type」という表示になっている。

インタフェースとIPアドレス設定 (デバイスから作成)

Deviceの登録をしただけだと、IPアドレスの設定箇所はない。
IPアドレスを設定するには、対象デバイスの管理画面の+ Add ComponentsからInterfacesIPアドレスを設定するインタフェースを追加する必要があるので、まずインタフェースを作成する。

f:id:zaki-hmkc:20210111162712p:plain

インタフェースの作成はインタフェース名とTypeを選ぶ。今どきの物理PCなら1000BASE-Tとかそんな感じのはず。

f:id:zaki-hmkc:20210111162942p:plain

インタフェースが作成できたら、IPアドレスを設定できる。
IPアドレスは予めIPAM -> IP Addressesで作成性済みのものを割り当てもできるが、インタフェースの画面から新規に作成することもできる。

f:id:zaki-hmkc:20210111163112p:plain

項目
Address そのインタフェースのIPアドレス/マスク値
Status 使ってるのでActive
Interface Assignment 今はデバイスの設定なのでDeviceでホスト名とインタフェース名を指定
primary IP for the device/VM メインのNICであればチェック

一覧で表示されるのはPrimary IPのため、primary IP for the device/VMのチェックが入ってないアドレスはデバイス一覧では確認できない。

f:id:zaki-hmkc:20210111163218p:plain

ちなみに重複したIPアドレスは登録は可能だが、IPAMの画面から見るとDuplicate IP Addressという表示になる。

f:id:zaki-hmkc:20210111163252p:plain

Virtual Machines

バイスの登録ができたので、今度はVMを登録してみる。
VMの登録に必要な必須パラメタは以下の通りで、デバイスに比べると項目は少ない。

  • Cluster
  • Cluster Type (Clusterの作成に必要)

ただし、Clusterの作成にはCluster Typeを作成しておく必要がある。

Cluster Types

Cluster TypeにはVMのプラットフォームの名称を入れておくとよさげ。
ドキュメントではVMware vSphereなど。
うちはvSphereは使ってないのでESXiとかHyper-Vとかのハイパーバイザーを登録しておいてみる。

f:id:zaki-hmkc:20210111163732p:plain

Cluster TypesClusterの親情報となり、デバイスにおけるManufacturersDevice Typesの関係に近い。

Clusters

VMの論理グループを作成できる。
名称とCluster Typeを選択するだけだが、デバイス情報に登録する際に必須だったSite情報もここに設定できる。

f:id:zaki-hmkc:20210111163826p:plain

自分の環境だとイマイチ使い道が(今のところ)ないので、(とりあえず)ESXiなどのポートグループ名などVMが所属するネットワークを入れておく。

1/28追記: スペースを含んでいてもNetBox上には登録できるが、NetBoxにAPIアクセスするツール側で処理できない場合があるので、アンダーバーを使うなどした方が無難。

VMの登録

必須項目であるClusterの準備ができたのでVMを登録する。
前述の通り、デバイスの登録時に使用したRoleも任意で指定できる。

f:id:zaki-hmkc:20210111164032p:plain

あとオプションだが、VMのリソース(CPUやメモリ、ストレージ)も設定できる。
(ただしストレージは一つのみなので、「ディスク2本載せている」みたいなのは現状では表現できなさそう)

f:id:zaki-hmkc:20210111164136p:plain

ちなみにこのmanagerはNetBoxを動かしてるホスト。

インタフェースとIPアドレス設定 (VMから作成)

VMを登録しただけだとまだIPアドレスは未設定・設定できないので、デバイスと同様に+ Add Interfacesからインタフェースを追加して、IPアドレスを設定する。

f:id:zaki-hmkc:20210111164329p:plain

インタフェースを追加したら、デバイスのときと同様に+ボタン押下で新しくIPアドレスを作成・設定できる。
Interface Assignmentについてはデバイスのとき異なり、Virtual Machineタブの方を設定する。

f:id:zaki-hmkc:20210111164802p:plain

これでVirtual Machinesの画面に戻ると、作成したVMの一覧が表示される。

f:id:zaki-hmkc:20210111164926p:plain

ちなみに、インタフェースの追加時にはens[192,225]のように入力すれば複数インタフェースをまとめて作成できる。

f:id:zaki-hmkc:20210111165011p:plain

IPアドレス作成 (IP Addressesから)

作成したIPアドレスの一覧はIPAM -> IP Addressesから確認できる。
また、この画面の+ Addから作成も可能。

f:id:zaki-hmkc:20210111165105p:plain

複数まとめて作成

IPアドレスの画面から作成する場合は、Bulk Createのタブから複数アドレスをまとめて作成もできる。

f:id:zaki-hmkc:20210111165314p:plain

DHCPのアドレスレンジ

アドレスレンジの対象を前述の複数アドレス登録を指定しつつ、StatusからActiveでなくDHCPを選ぶ。
「〇〇というPCやスマホは今このアドレスを使っている」というリース状態をNetBoxで管理するのは難しそうな気がする(DHCPサーバーのリース状況をNetBoxが監視できる?)けど、「少なくともこのアドレスはDHCPの管理下なのでstaticな設定で使っちゃダメ」というのは見てわかるようになる。

f:id:zaki-hmkc:20210111165359p:plain

もしかするとDHCPサーバー側がNetBoxからリースするアドレスレンジを取得する、というのはできたりするかも?(詳しくチェックはしてないけど、netbox dnsmasqとかisc-dhcp-server netboxググるとそれっぽい情報がヒットする)

Kubernetes + MetalLBで使用するLoadBalancer Service用のIPアドレスもこの辺で予約しておくと良さそう。

IPアドレス使用状況

f:id:zaki-hmkc:20210111165530p:plain

IPAM -> IP Addressesから作成済みIPアドレスの一覧は確認できるが、「誰が使ってるのか」「未定義(未作成)のIPアドレスがどれくらいあるのか」はパッとわからなかったり分かりづらい。
これはIPAM -> Prefixesを使うと俯瞰しやすい一覧を作れる。

+ AddでPrefixの作成画面から以下のように入力。

項目
Prefix 一覧対象にするネットワークアドレス(192.168.0.0/24など)
Status Active
Is a pool チェック無し
Site 必要に応じて作成済みSite

※ Is a poolはチェックすると、ネットワークアドレスとブロードキャストアドレスに相当するアドレス(上の例だと192.168.0.0192.168.0.255)も"Available"扱いになるので扱いには注意。「ネットワーク全体」をNetBoxで管理する場合はチェックを外し、ネットワーク管理者は別に存在して「あなた(あなたのチーム)はこのアドレスを自由に使ってどうぞ」と言われて頭から尻尾まで全部ホストに割り当てられるアドレスを管理するのであればチェックすれば良いと思う。

f:id:zaki-hmkc:20210111165636p:plain

作成すると192.168.0.0/24ネットワークの定義画面になり、この画面の「IP Addresses」を開くと、

  • 未使用のアドレスとその数
  • 使用済みアドレスの使用インタフェースとその親リソース(VMやデバイス)

が簡単に一覧から確認できる。 ネットワークマスクを調整し、サブネットワークアドレスについてもここで一覧にできる。

f:id:zaki-hmkc:20210111165727p:plain


APIなど (リンク)

tekunabe.hatenablog.jp

qiita.com

qiita.com

REST API使って参照や追加したり、Ansibleの入力(ダイナミックインベントリ)に使ったりもできるけど一旦ここまで。(→ ざっくり試してみた)
(画面キャプチャや説明文書きつつ手動操作してたというより先日のPC設定崩壊の復旧で力尽きた)

NetBoxを手動で使うとIPアドレス管理台帳.xlsxからツールが変わっただけ(で登録についてはむしろ手間がちょっと多い)だけど、NetBoxのAPIと連携することで、VM作成時に情報の登録を自動化に組み込んだり、メンテナンスや監視の対象ホストをNetBoxから動的に取得したりすることが可能になる、はず。つまり、管理の自動化と相性が良い

Prefixの画面だけでもExcelの管理台帳から入れ替えできる情報量になっててよさそう。


まだまだ使い始めたばかりなので「NetBox使うのに〇〇機能を使わないのはおかしい」とか「その機能はそうじゃなくてこう使うんだよ」とか色々あるかもしれないけど、「とりあえず入れてみて手を動かしてみた」をやらないと前に進まないので試してみた記録。

ちなみに自宅で運用してたIPアドレス管理台帳(Googleスプレッドシート)はこんな感じ。(一部)
だいぶカオスってる。

f:id:zaki-hmkc:20210111213023p:plain


1/28追記: 本文中にも書いているが、スペースを含んでいてもNetBox上には登録できるが、NetBoxにAPIアクセスするツール側で処理できない場合があるので、アンダーバーを使うなどした方が無難。

[Ansible] ターゲットホストのIPアドレスの参照とデフォルトゲートウェイの設定についてのメモ

Ansibleの実行の際に、設定ファイルを作ったりしたい場合に使えると便利なターゲットホストのIPアドレスの参照方法について。
内容は「動かしてみてその内容をまとめたもの」なので、ドキュメントやソースコードを追ったりまでは確認できていないです。

playbook

- hosts: gateway-test
  gather_facts: true
  gather_subset:
    - network

  tasks:
  - name: print ansible_host
    debug:
      msg:
        - "{{ ansible_host }}"
        - "{{ inventory_hostname }}"
        - "{{ ansible_facts.default_ipv4 }}"  # <- gather_facts: false だと参照不可

インベントリファイルは以下の通り

inventory_hostnameは、インベントリファイルに記載しているホスト名がセットされる。
ansible_hostは、インベントリファイルのホスト名に対してansible_host=でアドレスを明記しているとその値がセットされる。指定が無い場合はinventory_hostnameと同じホスト名がセットされる。

ansible_facts.default_ipv4gather_factsが有効の場合にセットされる。オフの場合は(ansible_facts自体はあるがdefault_ipv4が)undefinedとなる。

なお、factsの取得はネットワーク関連のみで良い場合は、

  gather_subset:
    - network

を追加することで対象情報を絞ることができる。
(詳しくは、Red Hat 齊藤秀喜さんの「はじめてのAnsibleトラブルシューティング」スライド46ページ参照)

www.slideshare.net


以下は、default_ipv4には何がセットされるのかについてのメモ。

ターゲットホストのOS設定

アドレス設定

処理対象のターゲットホストはCentOS 7でNICが2つ。
設定は以下の通り。

[zaki@vm-example ~]$ nmcli c s "System ens192" | grep ipv4
ipv4.method:                            manual
ipv4.dns:                               192.168.0.19
ipv4.dns-search:                        --
ipv4.dns-options:                       ""
ipv4.dns-priority:                      0
ipv4.addresses:                         192.168.0.37/24
ipv4.gateway:                           192.168.0.1
ipv4.routes:                            --
ipv4.route-metric:                      -1
ipv4.route-table:                       0 (unspec)
ipv4.routing-rules:                     --
ipv4.ignore-auto-routes:                いいえ
ipv4.ignore-auto-dns:                   いいえ
ipv4.dhcp-client-id:                    --
ipv4.dhcp-timeout:                      0 (default)
ipv4.dhcp-send-hostname:                はい
ipv4.dhcp-hostname:                     --
ipv4.dhcp-fqdn:                         --
ipv4.never-default:                     いいえ
ipv4.may-fail:                          はい
ipv4.dad-timeout:                       -1 (default)
[zaki@vm-example ~]$ nmcli c s "System ens224" | grep ipv4
ipv4.method:                            manual
ipv4.dns:                               172.16.1.0
ipv4.dns-search:                        --
ipv4.dns-options:                       ""
ipv4.dns-priority:                      0
ipv4.addresses:                         172.16.0.37/23
ipv4.gateway:                           --
ipv4.routes:                            --
ipv4.route-metric:                      -1
ipv4.route-table:                       0 (unspec)
ipv4.routing-rules:                     --
ipv4.ignore-auto-routes:                いいえ
ipv4.ignore-auto-dns:                   いいえ
ipv4.dhcp-client-id:                    --
ipv4.dhcp-timeout:                      0 (default)
ipv4.dhcp-send-hostname:                はい
ipv4.dhcp-hostname:                     --
ipv4.dhcp-fqdn:                         --
ipv4.never-default:                     いいえ
ipv4.may-fail:                          はい
ipv4.dad-timeout:                       -1 (default)

default gatewayが設定されているのはens192のみ。

ens192のみgateway設定

前述の設定の状態でansible_facts.default_ipv4の値を参照してみる。

[zaki@vm-example ~]$ nmcli c s "System ens192" | grep ipv4.gateway
ipv4.gateway:                           192.168.0.1
[zaki@vm-example ~]$ nmcli c s "System ens224" | grep ipv4.gateway
ipv4.gateway:                           --
TASK [print ansible_host] ****************************************************************************
ok: [192.168.0.37] => 
  msg:
  - 192.168.0.37
  - 192.168.0.37
  - address: 192.168.0.37
    alias: ens192
    broadcast: 192.168.0.255
    gateway: 192.168.0.1
    interface: ens192
    macaddress: 00:50:56:01:23:45
    mtu: 1500
    netmask: 255.255.255.0
    network: 192.168.0.0
    type: ether

デフォルトゲートウェイが設定されているens192のネットワーク情報が取れている。

ens226のみgateway設定

デフォルトゲートウェイの設定をens192からens224に設定し直す。

[zaki@vm-example ~]$ sudo nmcli c m "System ens192" ipv4.gateway ""
[zaki@vm-example ~]$ sudo nmcli c m "System ens224" ipv4.gateway 172.16.1.0
[zaki@vm-example ~]$ nmcli c s "System ens192" | grep ipv4.gateway
ipv4.gateway:                           --
[zaki@vm-example ~]$ nmcli c s "System ens224" | grep ipv4.gateway
ipv4.gateway:                           172.16.1.0
[zaki@vm-example ~]$ sudo systemctl restart network
TASK [print ansible_host] ****************************************************************************
ok: [192.168.0.37] => 
  msg:
  - 192.168.0.37
  - 192.168.0.37
  - address: 172.16.0.37
    alias: ens224
    broadcast: 172.16.1.255
    gateway: 172.16.1.0
    interface: ens224
    macaddress: 00:0c:29:f0:53:1f
    mtu: 1500
    netmask: 255.255.254.0
    network: 172.16.0.0
    type: ether

デフォルトゲートウェイ設定を削除したens192ではなく、新たに設定したens224のネットワーク情報が取れている。

gateway設定無し

デフォルトゲートウェイの設定を全て削除してみる。

[zaki@vm-example ~]$ sudo nmcli c m "System ens192" ipv4.gateway ""
[zaki@vm-example ~]$ sudo nmcli c m "System ens224" ipv4.gateway ""
[zaki@vm-example ~]$ nmcli c s "System ens224" | grep ipv4.gateway
ipv4.gateway:                           --
[zaki@vm-example ~]$ nmcli c s "System ens192" | grep ipv4.gateway
ipv4.gateway:                           --
[zaki@vm-example ~]$ sudo systemctl restart network
TASK [print ansible_host] ****************************************************************************
ok: [192.168.0.37] => 
  msg:
  - 192.168.0.37
  - 192.168.0.37
  - {}

値が入ってない。
(ansible_facts.default_ipv4という変数自体はあるが中身が空)

両方にgateway設定

そもそもこの設定はアリなのか?というのは置いておいて…

[zaki@vm-example ~]$ sudo nmcli c m "System ens192" ipv4.gateway "192.168.0.1"
[zaki@vm-example ~]$ sudo nmcli c m "System ens224" ipv4.gateway 172.16.1.0
[zaki@vm-example ~]$ nmcli c s "System ens192" | grep ipv4.gateway
ipv4.gateway:                           192.168.0.1
[zaki@vm-example ~]$ nmcli c s "System ens224" | grep ipv4.gateway
ipv4.gateway:                           172.16.1.0
[zaki@vm-example ~]$ sudo systemctl restart network
TASK [print ansible_host] ****************************************************************************
ok: [192.168.0.37] => 
  msg:
  - 192.168.0.37
  - 192.168.0.37
  - address: 192.168.0.37
    alias: ens192
    broadcast: 192.168.0.255
    gateway: 192.168.0.1
    interface: ens192
    macaddress: 00:50:56:01:23:45
    mtu: 1500
    netmask: 255.255.255.0
    network: 192.168.0.0
    type: ether

ens192の値が取れてる。1番目のNICかな?
(でも、そもそもNICに順番ってあったっけ)

この辺りの仕様について書かれたドキュメントはちょっと見つけられず。

インタフェースを指定して参照

setupなどを使って収集したfactsの値を見ていると、ansible_default_ipv4以外にansible_ens192ansible_ens224はあるので、これを参照すれば良い。
システムによってはインタフェース名はens192でなくeth0などになるかもしれない。
(手元の環境はVMware ESXi上のVM)

インタフェース名を指定

  tasks:
  - name: print ens224.ipv4
    debug:
      msg: "{{ ansible_facts.ens224.ipv4 }}"

ens224を直接指定(ハードコーディング)してPlaybookを実行すると、

ok: [192.168.0.37] => 
  msg:
    address: 172.16.0.37
    broadcast: 172.16.1.255
    netmask: 255.255.254.0
    network: 172.16.0.0

この通り取得できる。
このときデフォルトゲートウェイの設定に関係なくIPアドレス情報は取得できるが、デフォルトゲートウェイの設定は.ipv4の中には含まれない。

インタフェースリスト名も取得

インタフェースの一覧はansible_facts.interfacesで取得できる。

  - name: print interfaces
    debug:
      msg: "{{ ansible_facts.interfaces }}"

これで実行すると

ok: [192.168.0.37] => 
  msg:
  - lo
  - ens192
  - ens224

なのでこのリストからインタフェース名をピックアップし、それを使って ansible_fact[〇〇].ipv4とすれば動的に取得することもできる。

  - name: print ipv4 address (nic1)
    vars:
      nic: "{{ ansible_facts.interfaces[1] }}"
    debug:
      msg: "{{ ansible_facts[nic].ipv4 }}"

一度varsでインタフェース名リストの2番の項目(ens192)を変数nicにセットし、そのipv4アドレスを取得。

ok: [192.168.0.37] => 
  msg:
    address: 192.168.0.37
    broadcast: 192.168.0.255
    netmask: 255.255.255.0
    network: 192.168.0.0

このようにens192というインタフェース名をplaybook側で知っていなくても取得は可能。