vscode とjupyter notebook x ansibleの連携環境構築

# はじめに

VS Codeは開発者としてなじみ深いものですが、インフラ系の方は意外と使っている人が少ないようです。
今はインフラの構築手順をドキュメントとして残すのでは無く、IaC(Infrastructure as Code)の考えに沿ってコード化しようという動きがawsさんの方で積極的に行われていました。最近ですとaws cdkなんかが便利でいいですね。cloud formationの構成ファイルは正直書くのが辛かった。全然話はそれますが、AWS SAP資格持ちですが、11月に有効期限切れを迎えるので10月ぐらいには再勉強に関する記事も載せていこうかなと思っています。

本題に戻ると、IaCの考えで手順をコード化するのであれば開発者と同じようにIDE（統合開発環境）も使いますし、コードの変更履歴（github)の管理も行います。そしてIDEでコードを書くのであればgithub copilotなどを使う事が出来るのでchatgptに指示をするより簡単です（複数開いているファイルやターミナル情報を元に回答してくれる）

私の場合は、openhpcの構成管理・slurm検証環境の構築のためにVS Code経由でコンテナ化したJupyter notebookに接続し、github Copilotの助けを借りて、slurmのワーカーノード用にansible-playbookを作成します。そしてjupyterからansibleを起動しワーカーノードに反映する所まで実行したら、notebookを履歴として保存します。

ansible経由で配布するslurm用のパッケージは[openhpc x slurm x beeGFS環境の構築と検証:ワーカーノード用のパッケージ化](https://qiita.com/kazuharu2022/items/45d936684b70c90f6411)にて作成したものを使用します。
パッケージ作成するためのspecファイル作成や実行もjuypter notebookでやれば良かったと今更気づきました。

### IaCのための環境イメージ
![スクリーンショット 2024-09-05 23.08.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/f0b94555-0d9c-5259-e2e4-98fb7c206908.png)

### Githubの準備
ここら辺は他の方の記事を参考にして作成してください。
[githubアカウントの作成](https://qiita.com/Baru_Try/items/e80a765919a35a3c28c7)

IaCで行うとgithubへのパスワードもコードとして埋め込みます。
しかしパスワードをそのまま埋め込むこと自体がパスワードの意味を無してしまうので、パスワードの代わりとしてアクセストークン（私はこれを入場チケットと新人には教えてます）を発行し、それをコードに埋め込みます。
こちらも他の方を参考に発行してみてください。
[githubアクセストークンの作成](https://qiita.com/LaP1s/items/69409c6bf08f8c7bca48)

最後にコードの履歴を保存するリポジトリを作成します。

インフラをメインにしている人はリポジトリの単位をどうしたらいいのか分かりずらいですが、基本はプロジェクト単位、運用単位で行う事をお勧めします。
タスク単位まで分割してしまうとちょっと切替が面倒かなとは思いますが厳密に分けたい場合はタスク単位でも良いかもしれません。その場合はjupyter notebook x ansibleのコンテナもリポジトリ単位で作成しておかないとミスが発生しやすくなります。

[リポジトリ作成](https://docs.github.com/ja/repositories/creating-and-managing-repositories/quickstart-for-repositories)


### juypter notebook と AnsibleのDockerfile作成

ではgithubの準備も出来たので、次にはjupyter notebookとAnsible そしてgitが使えるコンテナを作成します。

っとそのまえにdockerとdocker-composeもインストールしていない場合は事前にインストールしてください。

手順はこちらを参考にしてください。
[dockerのインストール]()

私は検証環境なのでrootでやってしまいますが、個人アカウントを使う場合はインストール後にdockerグループに追加してください。

[dockerのインストール](https://qiita.com/yasushi-jp/items/976df81fd8ee8ee65f29)
[docker-composeのインストール](https://qiita.com/yasushi-jp/items/8281540d0a874cfafa2b)


``` sudo usermod -aG docker ユーザ名 ```

ではようやく本題のjupyter notebook , ansible , gitがインストールされたイメージファイルを作成します。
ファイルの作成場所は共有のワークスペースに保存してください。
私の場合はNFSで共有して居る/shareなどに、githubのプロジェクト名と同じ名前のサブフォルダを作成し、さらにdockerfiles/[イメージ名]の下にDockerfileをおきます。
例:```/share/PJ_OpenHPC_test/dockerfiles/jupyter_ansible```


下記のDockerfileではツールのインストール以外に、作業ディレクトリ(カレントディレクトリ）に/workapceを指定しjupyterをどこからでも接続して良い設定で起動するようにしています

```bash:Dockerfile
FROM python:3.9-slim

RUN apt-get update && \
    apt-get install -y sshpass openssh-client git && \
    apt-get clean

RUN pip install ansible

RUN pip install notebook

WORKDIR /workspace

CMD ["jupyter", "notebook", "--ip=0.0.0.0", "--no-browser", "--allow-root"]

```
以下のケースではDockerfileという名前のファイルに構成を記述しないとイメージファイルを作成出来ないので注意してください。-f オプションを使ってファイル名を指定することで任意のファイル名でbuild出来ます。

このコマンドでコンテナイメージファイルを作成します。

```bash
docker build -t jupyter_ansible:latest .

# Dockerfileではなく任意のファイル名にする場合は
# docker build -t jupyter_ansible:latest -f <任意のファイル名>
```

コンテナを起動する際の物理ボリュームとコンテナボリュームのマッピングや、ポートのマッピングなどはdocker-compose.ymlに記述します。<ユーザ名>部分は分かりやすい名前に置き換えてください。別に名前でなくても問題ないです。

```bash:docker-compose.yml
version: '3'
services:
  ansible-jupyter:
    image: ansible-jupyter-container:latest
    ports:
      - "8888:8888"
    volumes:
      - /share/PJ_jupyter_ansible_test:/workspace 
      - /root/.ssh:/root/.ssh 
    environment:
      - JUPYTER_TOKEN=<ユーザ名>
    command: >
      bash -c "jupyter notebook --ip=0.0.0.0 --no-browser --allow-root --NotebookApp.token=${JUPYTER_TOKEN}"

```
docker composeもdocker-compose.yml以外でマッピング情報を記載するときは-fオプションを指定してください。

```bash
docker-compose up -d

# docker-compose.yml以外の名前で作成した場合は
# docker-compose up -d -f <任意のファイル名>
```

まずはこの状態で、起動確認をします。
ブラウザからhttp://<dockerが動作しているホストのIP>:8888/?token=<ユーザ名>

起動出来ると下図のような画面が出ます。右端の「New」をクリックし「Python 3(pykernel)」を選択してください。

![画面3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/f8b3ca73-8c62-44b5-8ae9-369d1facd96d.png)

そうすると、新しいnotebookファイルが作成されます。

jupyter notebookの使い方は他のブログを参照してください。
下図にあるとおり、イメージファイル作成時に指定したツールとカレントディレクトリが有効かをチェックします。これでエラーがなければ完了です。

![画面4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/1843af9c-c57a-bf47-787e-ddac842c0e1a.png)


### vscodeの準備
最終的には、VS CODE側からSSH接続でファイルにアクセスし、jupyter notebookにはWEB:8888ポートから接続する構成になります。

ややこしい構成にしていますが、作業端末側にファイルを分散させずホスト側でファイルを管理してかったからという理由になります。（強いて言えばやってみたかった）

複数人で一つのプロジェクトをメンテナンスする場合はこの構成は良くないので、
シンプルにVS CODEからSSH接続せず作業端末側でgithubからリポジトリのコピー（クローン）を作成した上で、実行のみJupyter経由でさせた方が良いです。


![スクリーンショット 2024-09-06 16.46.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/280a417c-27b6-84de-811d-fce46e568981.png)


では具体的なインストール方法はまた他のブログを頼って省略します。まずはVS CODE自体のインストール自体はすんなり入ります。
私は普段m1macを使っていますが一般的にはwindows10/11を使っている方が多いと思いますので、下記サイトを参考にインストールをお願いします。

[VS Codeのインストール](https://qiita.com/mmake/items/2cf2131a0ab5bc431215)

VS Codeから参照するipynbファイルなどはコンテナを動かしているホストに置いておき、
githubと連携したいので、vscodeからリモート接続するためのRemote SSHをインストールします。
下記サイトを参考に追加をお願いします。

[remore sshを追加](https://qiita.com/nlog2n2/items/1d1358f6913249f3e186)

今回はややこしい構成の方としてSSH接続を行い、ファイルはホスト側に置きます。
左の赤枠のアイコンをクリックすると下図のようにSSHの画面になりますカーソルを合わせると右側に「＋」印がでますのでクリックします。
remoto sssh![スクリーンショット 2024-09-06 16.18.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/c97a9d20-db18-81c4-39b1-ac9434e67127.png)

普通のSSH接続をします。 私の場合は秘密鍵をed25519を事前に作成し、ホストに配布しているので指定しています。ここらへんは、一般的にはRSAで作る人も多いようです。

ここは省略しますが、後日カギの作成方法は別途掲載します。

![スクリーンショット 2024-09-06 16.20.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/b89448cf-e8f0-c773-0066-3af6b731b4cc.png)

設定をドコに保存するか聞かれている画面です。私はm1macなので下図みたいになります。
（保存先は個人にしています）
![スクリーンショット 2024-09-06 16.20.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/f97ddbb6-5773-31f0-a0bd-f2a5225049ac.png)

そうすると左側にホストが追加されます。赤枠で囲まれたアイコンをクリックして別ウィンドウでVS CODEを立ち上げます。
![スクリーンショット 2024-09-06 16.21.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/d62cf6b7-72ca-253a-4406-ba0fb4856a92.png)

途中でパスワードを聞かれます（上に表示されてますのでお見逃し無く）
接続出来ると下図のように新しいVS CODEが立ち上がります。

![スクリーンショット 2024-09-06 16.22.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/11c5dbfa-c0dd-a192-d26f-ed7d07229edb.png)

次に作業フォルダへ 場所はプロジェクトディレクトリになります。
今回の例で言えば/workspace/PJ_jupyter_ansible_testになります。

![スクリーンショット 2024-09-06 16.22.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/78a548ab-5bc6-da30-a6ac-569a8b78b933.png)

下図の通りチェックボックスをONにして、「はい、作成者を信頼します」をクリックしてください
![スクリーンショット 2024-09-06 17.45.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/10a66775-60f7-3835-d193-5bb9a6d57699.png)

そうすると、フォルダが下図のように表示されます。（現在からっぽ）
![スクリーンショット 2024-09-06 17.46.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/e6dabb45-eef8-3579-0176-dd9fd2d98ea9.png)

一つ忘れてましたマーケットプレイスでjupyterの拡張モジュールを入れてください。

![スクリーンショット 2024-09-06 15.04.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/cde451f3-1f40-aa67-341e-7419943a4240.png)

ようやくjupyterに接続する準備が整ったので、下図のように選択してJupyterに接続します。
下図のように Create:New Jupyter Notebookを選択してください。
![スクリーンショット 2024-09-06 17.59.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/3684ed23-faa2-aa78-3995-88e2944bd23c.png)

画面が開いたら右端にある「カーネルを検出する」をクリックしてください。
![スクリーンショット 2024-09-06 18.00.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/868d7414-0933-3c66-0352-7d7850b42dd1.png)

「既存のJupyter サーバー...」を選択します。
![スクリーンショット 2024-09-06 18.00.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/044cfa50-9a77-9e88-b582-5db50b8ed326.png)

Jupyter サーバーの選択では、ブラウザで確かめたときと同じURLを入れます。
http://<ホストのIP>:8888/?token=<ユーザ名>
![スクリーンショット 2024-09-06 18.01.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/5d2d5272-740a-49da-47db-f581cfea381d.png)

表示名はホスト名にしておきます
![スクリーンショット 2024-09-06 18.02.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/ca2c4533-1087-a976-aa05-89dbe9d41491.png)

ここでおすすめを選ぶとうまく実行できないので、すでにブラウザから開いていたファイルを指定してください。ここだと真ん中の「Python 3 (ipykernel)(Untitled.ipynb)」になります。
![スクリーンショット 2024-09-06 18.02.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/35f9ed07-4f03-6e47-8277-788a104ca532.png)

起動後は下図のようにgithubに接続し、フォルダを作成。
そしてDockerfile,docker-compose.ymlをコピーしておきます。
ちなみにこのノートブックをgithubにアップロードは出来ません。アクセストークンが含まれているとポリシーエラーとなってgithubにプッシュ出来なくなりますので気をつけてください。

![スクリーンショット 2024-09-06 19.14.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/853b7e7c-a20c-c90f-10e2-1432847d3422.png)

### slurmワーカーノード用のplaybook作成

長かったですが、ようやくここから本題です。
PJ_jupyter_ansible_testの直下に以前buildしたパッケージをコピーします。
[slurmのパッケージ作成はこちらを見てください](https://qiita.com/kazuharu2022/items/45d936684b70c90f6411)

```bash
cd
cp -r rpmbuild /workspace/PJ_jupyter_ansible
```

また配布先のワーカーノード(slurm01 rockylinux8.8)には、コンテナからSSH接続出来るように既にある秘密鍵と公開鍵をコンテナにコピーして置きます。その上でコンテナ内から一度実行出来るか確認をして置きます。

```bash
#今回であればplaybooks/etc/に公開鍵と秘密鍵を入れて置いてください。
ssh-copy-id -i id_ed25519.pub root@<ホストIP> 
ssh -i id_ed25519 root@<ホストIP>
```

github copilotをインストールしているとjupyter notebook上からcopilotを利用出来ます。

私自身の使い方は全部一気に指示を書くのでは無くSTEP BY STEPでちょっとずつ追加しながら作成していきます。
chatgpt/copilotで作成することで、動くplaybookは作成出来ます。しかし```変更```には弱いです。
リファクタリング的な考えで、訪れるか分からない将来を見越して余計な設計するよりも、ちょっとずつ変更に強いplaybookを作っていくことをお勧めします。
今回は取り上げないですが、変更がちょこちょこ入るようなサービスがある場合playbookファイルをサービス単位に分割して置くやり方も一つの方法です。

```yml:rocky88_slurmd.yml
---
- name: Manage slurm nodes
  hosts: slurm
  become: true  # root権限で実行
  tasks:

    - name: Update all packages
      dnf:
        name: '*'
        state: latest
        update_cache: true

    - name: Upgrade nfs-utils to the latest version
      dnf:
        name: nfs-utils
        state: latest

    - name: Install NIS and related packages
      dnf:
        name:
          - ypbind
          - rpcbind
          - oddjob-mkhomedir
        state: present

    - name: Set NIS domain name
      command: ypdomainname lmnis

    - name: Add NISDOMAIN to /etc/sysconfig/network
      lineinfile:
        path: /etc/sysconfig/network
        line: "NISDOMAIN=lmnis"
        create: yes

    - name: Add NIS server to /etc/yp.conf
      lineinfile:
        path: /etc/yp.conf
        line: "domain lmnis server beegfs01"
        create: yes

    - name: Select NIS profile with authselect
      command: authselect select nis --force

    - name: Enable mkhomedir with authselect
      command: authselect enable-feature with-mkhomedir

    - name: Set SELinux boolean nis_enabled to on
      seboolean:
        name: nis_enabled
        state: true
        persistent: yes
      when: ansible_facts['selinux']['status'] == 'enabled'
      
    - name: Enable and start NIS-related services
      systemd:
        name: "{{ item }}"
        enabled: true
        state: started
      loop:
        - rpcbind
        - ypbind
        - nis-domainname
        - oddjobd

    - name: Disable and stop firewalld
      systemd:
        name: firewalld
        enabled: false
        state: stopped

    - name: Disable SELinux
      selinux:
        state: disabled

    - name: Reboot the system to apply SELinux changes
      reboot:
        msg: "Rebooting to apply SELinux changes"
        connect_timeout: 5
        reboot_timeout: 300
      when: ansible_facts['selinux']['status'] == 'enabled'

    - name: Install required packages
      dnf:
        name:
          - ncurses-devel
          - openssl-devel
          - gcc-c++
          - gcc-gfortran
          - coreutils
        state: present

    - name: Create /etc/profile.d/openmpi.sh
      copy:
        dest: /etc/profile.d/openmpi.sh
        content: |
          export PATH=$PATH:/opt/openmpi-5.0.3/bin
          export MANPATH=$MANPATH:/opt/openmpi-5.0.3/man
        owner: root
        group: root
        mode: '0644'

    - name: Add entries to /etc/hosts
      blockinfile:
        path: /etc/hosts
        block: |
          <ホストのIP> beegfs01
          <ホストのIP> master01
          <ホストのIP> slurm01

    - name: Add entry to /etc/fstab
      blockinfile:
        path: /etc/fstab
        block: |
          beegfs01:/share /share nfs defaults 0 0

    - name: Create /share directory
      file:
        path: /share
        state: directory
        owner: root
        group: root
        mode: '0755'

    - name: Mount /share
      mount:
        path: /share
        src: beegfs01:/share
        fstype: nfs
        opts: defaults
        state: mounted

    - name: Create symbolic link /workspace -> /share
      file:
        src: /share
        dest: /workspace
        state: link
    
    - name: Remove tmux temporarily to resolve libevent dependency
      dnf:
        name: tmux
        state: absent

    - name: Install libevent RPM packages from local folder
      yum:
        name:
          - /workspace/PJ_jupyter_ansible_test/rpmbuild/RPMS/x86_64/libevent-2.1.12-1.el8.x86_64.rpm
          - /workspace/PJ_jupyter_ansible_test/rpmbuild/RPMS/x86_64/libevent-debuginfo-2.1.12-1.el8.x86_64.rpm
          - /workspace/PJ_jupyter_ansible_test/rpmbuild/RPMS/x86_64/libevent-debugsource-2.1.12-1.el8.x86_64.rpm
          - /workspace/PJ_jupyter_ansible_test/rpmbuild/RPMS/x86_64/hwloc-2.10.0-1.el8.x86_64.rpm
          - /workspace/PJ_jupyter_ansible_test/rpmbuild/RPMS/x86_64/hwloc-debuginfo-2.10.0-1.el8.x86_64.rpm
          - /workspace/PJ_jupyter_ansible_test/rpmbuild/RPMS/x86_64/hwloc-debugsource-2.10.0-1.el8.x86_64.rpm
          - /workspace/PJ_jupyter_ansible_test/rpmbuild/RPMS/x86_64/pmix-4.2.9-1.el8.x86_64.rpm
          - /workspace/PJ_jupyter_ansible_test/rpmbuild/RPMS/x86_64/openmpi-5.0.5-1.el8.x86_64.rpm
          - /workspace/PJ_jupyter_ansible_test/rpmbuild/RPMS/x86_64/openmpi-debuginfo-5.0.5-1.el8.x86_64.rpm
          - /workspace/PJ_jupyter_ansible_test/rpmbuild/RPMS/x86_64/openmpi-debugsource-5.0.5-1.el8.x86_64.rpm
          - /workspace/PJ_jupyter_ansible_test/rpmbuild/RPMS/x86_64/ucx-1.17.0-1.el8.x86_64.rpm
          - /workspace/PJ_jupyter_ansible_test/rpmbuild/RPMS/x86_64/ucx-debuginfo-1.17.0-1.el8.x86_64.rpm
          - /workspace/PJ_jupyter_ansible_test/rpmbuild/RPMS/x86_64/ucx-debugsource-1.17.0-1.el8.x86_64.rpm
          - /workspace/PJ_jupyter_ansible_test/rpmbuild/RPMS/x86_64/munge-0.5.15-1.el8.x86_64.rpm
          - /workspace/PJ_jupyter_ansible_test/rpmbuild/RPMS/x86_64/munge-debuginfo-0.5.15-1.el8.x86_64.rpm
          - /workspace/PJ_jupyter_ansible_test/rpmbuild/RPMS/x86_64/munge-debugsource-0.5.15-1.el8.x86_64.rpm
          - /workspace/PJ_jupyter_ansible_test/rpmbuild/RPMS/x86_64/slurm-23.11.10-1.el8.x86_64.rpm
          - /workspace/PJ_jupyter_ansible_test/rpmbuild/RPMS/x86_64/slurm-debuginfo-23.11.10-1.el8.x86_64.rpm
          - /workspace/PJ_jupyter_ansible_test/rpmbuild/RPMS/x86_64/slurm-debugsource-23.11.10-1.el8.x86_64.rpm
          
        state: present
        disable_gpg_check: true 

    - name: Reinstall tmux
      dnf:
        name: tmux
        state: present
```
VSCODEから実行するとこんな感じです。

![スクリーンショット 2024-09-09 13.21.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/1698a0b7-fb09-b2f4-f6b0-37c89d555e75.png)

〜〜〜〜〜〜　中略　〜〜〜〜〜〜〜

![スクリーンショット 2024-09-09 13.21.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/26f20a0b-cd29-3d2a-0c0b-7ca5feee154a.png)

#### 残作業
かなり長くなってしまったので、別のブログにplaybookファイルを分割して、
メンテナンス性を上げつつ、残りのサービス設定を次回行います。