LoginSignup
0

posted at

updated at

http経由でスクリプトを取得し実行するcloud-initのmoduleを作ってみる

さくらのアドベントカレンダー2022 12日目の記事です。(すみません。12日に書いたものではないです)

さくらインターネットの稲垣です。
本記事ではcloud-initのモジュールの作成の仕方と、そのモジュールを使えるマイアーカイブの作成について紹介します。

今回は、urlを指定するとスクリプトをダウンロードして実行するモジュールを作成します。

開発環境

Ubuntu 20.04で開発を行いました。

moduleのスクリプトを作成する

cloud-initをgit cloneする

git clone https://github.com/canonical/cloud-init.git
cd cloud-init/

モジュールのソースコードを作成する

cloudinit/config/ディレクトリにcc_というプレフィクスをつけたファイルを作成します。

今回はcc_http_scripts.pyというファイルで作成しました。

vim cloudinit/config/cc_http_scripts.py

内容を以下のように編集します。(公式ドキュメントをベースに変更しています。)

基本的にやりたいことをhandle関数に書いていきます。
metaidにはcc_http_scriptsを、activate_by_schema_keysには["http_scripts"]を指定します。

# This file is part of cloud-init. See LICENSE file for license information.
"""Example Module: Shows how to create a module"""
import sys
import urllib.request
import subprocess
from subprocess import PIPE, STDOUT, CalledProcessError
from logging import Logger

from cloudinit.cloud import Cloud
from cloudinit.config import Config
from cloudinit.config.schema import MetaSchema, get_meta_doc
from cloudinit.distros import ALL_DISTROS
from cloudinit.settings import PER_INSTANCE

MODULE_DESCRIPTION = """\
Description that will be used in module documentation.

This will likely take multiple lines.
"""

meta: MetaSchema = {
    "id": "cc_http_scripts",
    "name": "Example Module",
    "title": "Shows how to create a module",
    "description": MODULE_DESCRIPTION,
    "distros": [ALL_DISTROS],
    "frequency": PER_INSTANCE,
    "activate_by_schema_keys": ["http_scripts"],
    "examples": [],
}

__doc__ = get_meta_doc(meta)


def handle(
    name: str, cfg: Config, cloud: Cloud, log: Logger, args: list
) -> None:
    http_scripts = cfg.get("http_scripts", [])
    for http_script in http_scripts:
        if "url" not in http_script:
            raise ValueError(f"Missing required key 'url' from {http_script}")

        url = http_script.get("url")
        req = urllib.request.Request(url)
        with urllib.request.urlopen(req) as res:
            script = res.read()
            p = subprocess.run(
                ["/bin/sh"],
                input=script,
                stdout=PIPE,
                stderr=STDOUT,
            )
            if p.stdout:
                sys.stdout.write(p.stdout.decode("utf-8"))

設定ファイルのテンプレートを編集します。

vim config/cloud.cfg.tmpl

cloud_config_modules:http_scriptsを追記します。

$ git diff
diff --git a/config/cloud.cfg.tmpl b/config/cloud.cfg.tmpl
index fdd5a357..22bf6bd9 100644
--- a/config/cloud.cfg.tmpl
+++ b/config/cloud.cfg.tmpl
@@ -160,6 +160,7 @@ cloud_config_modules:
 {% if variant in ["ubuntu", "unknown", "debian"] %}
  - byobu
 {% endif %}
+ - http_scripts

 # The modules that run in the 'final' stage
 cloud_final_modules:

変更内容をコミットしておきます。

git add -A
git commit -m 'Add http_scripts'

debパッケージを作成する

まずは、依存パッケージをインストールします。

sudo apt update
sudo apt install make devscripts debhelper dh-python dh-systemd python3-pytest python3-pytest-cov python3-pytest-mock python3-responses python3-configobj python3-debconf python3-jinja2 python3-jsonpatch python3-jsonschema python3-oauthlib python3-serial python3-setuptools

Makefileにdebパッケージを作成するターゲットが用意されていためそちらを実行します。
また上記依存パッケージ以外にも必要なパッケージがある場合はエラー文中に表示してくれます。

make deb

以下のようにdebファイルが作成されていることが分かります。

$ ls -lah *.deb
-rw-r--r-- 1 ubuntu ubuntu 502K Dec 30 02:16 cloud-init_22.4-51-g75953781-1~bddeb_all.deb
lrwxrwxrwx 1 ubuntu ubuntu   44 Dec 30 02:16 cloud-init_all.deb -> cloud-init_22.4-51-g75953781-1~bddeb_all.deb

カスタムcloud-initをインストールしたアーカイブを作成する

先ほど作成したdebをインストールしてさくらのクラウドのマイアーカイブを作成します。

さくらのクラウドでマイアーカイブ作成用サーバーを作成する

ベースになるディスクを作成するだけなので最小限のスペックで作成しました。

項目 スペック
CPU 1コア
メモリ 1GB
ディスク容量 20GB
ネットワーク 共有
アーカイブ Ubuntu Server 20.04.2 LTS 64bit (cloudimg)

マイアーカイブ作成用サーバーにカスタムcloud-initをインストールする

debを作成した環境でscpを使いファイルをマイアーカイブ作成用サーバーに転送します。

scp cloud-init_22.4-51-g75953781-1~bddeb_all.deb ubuntu@<ip address>:~/

マイアーカイブ作成用サーバーでカスタムcloud-initをインストールします。

sudo apt install ./cloud-init_22.4-51-g75953781-1~bddeb_all.deb

cloud-initをリセットする

以下のようにcloud-initが実行済みになっているためリセットします。

$ cloud-init status
status: done
$ sudo cloud-init clean
$ cloud-init status
status: not run

シャットダウンしさくらのクラウドコンパネでマイアーカイブを作成する

sudo shutdown -h now

アーカイブソースにディスクを選択しマイアーカイブ作成用サーバーのディスクを選択します。

image.png

カスタムcloud-initインストール済みのマイアーカイブを使ってみる

ディスクからマイアーカイブへのコピーが完了するとマイアーカイブが利用可能になります。

マイアーカイブを使ってサーバーを作成する

現在さくらのクラウドではマイアーカイブをソースとする場合にコンパネからuser-dataを挿入することができません。
そのため、サーバーを起動せずに作成し、usacloudを利用してuser-dataを挿入してサーバーを起動します。

usacloudはさくらのクラウド用の公認CLIクライアントです。
※ 以下usacloudセットアップ済みの前提で進みます。

先ほど作成したアーカイブをマイアーカイブから選択します。

image.png

その他の設定項目で「作成後すぐに起動」を外しておきます。

image.png

usacloudでサーバーが作成されていることを確認します。

usacloud iaas server list

image.png

user-dataを作成しサーバーを起動する

user-dataを記述したファイルを作成します。

今回作成したモジュールを利用してdockerとk0sをインストールしてみます。

cat <<EOF > user-data
#cloud-config
users:
- name: ubuntu
  ssh_import_id: gh:<GitHub のユーザー名>
  lock_passwd: false
  shell: /bin/bash
  sudo: ALL=(ALL) NOPASSWD:ALL
  uid: 1000

http_scripts:
- url: https://get.docker.com
- url: https://get.k0s.sh
EOF

user-dataを指定して起動します。

usacloud iaas server boot my-cloud-init --zone=is1b --user-data=./user-data

カスタムcloud-initが動作していることを確認する

以下のコマンドで共有セグメントに接続しているIPアドレスでsshコマンドを実行できます。

usacloud iaas server ssh my-cloud-init

user-dataで指定したgithubに登録している公開鍵に対応する秘密鍵でsshができました。

Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-80-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of 2022年 12月 30日 金曜日 04:33:19 JST

  System load:  0.0               Processes:             96
  Usage of /:   7.7% of 19.21GB   Users logged in:       0
  Memory usage: 17%               IPv4 address for ens3: <ip address>
  Swap usage:   0%


0 updates can be applied immediately.

New release '22.04.1 LTS' available.
Run 'do-release-upgrade' to upgrade to it.


Last login: Fri Dec 30 04:26:45 2022 from <ip address>
ubuntu@sv-113402280888:~$

cloud-initが実行されていることを確認します。
--waitオプションをつけることで完了するまで待つことができます。

$ cloud-init status
status: running
$ cloud-init status --wait
................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
status: done

ログを確認する

cloud-initのログは/var/log/cloud-init.log/var/log/cloud-init-output.logがあります。
cloud-init.logにはcloud-init自体のログがcloud-init-output.logはモジュールのログが出力されます。

$ grep http_scripts /var/log/cloud-init.log
2022-12-29 19:48:51,729 - modules.py[DEBUG]: Running module http_scripts (<module 'cloudinit.config.cc_http_scripts' from '/usr/lib/python3/dist-packages/cloudinit/config/cc_http_scripts.py'>) with frequency once-per-instance
2022-12-29 19:48:51,729 - handlers.py[DEBUG]: start: modules-config/config-http_scripts: running config-http_scripts with frequency once-per-instance
2022-12-29 19:48:51,729 - util.py[DEBUG]: Writing to /var/lib/cloud/instances/nocloud/sem/config_http_scripts - wb: [644] 24 bytes
2022-12-29 19:48:51,730 - helpers.py[DEBUG]: Running config-http_scripts using lock (<FileLock using file '/var/lib/cloud/instances/nocloud/sem/config_http_scripts'>)
2022-12-29 19:51:03,066 - handlers.py[DEBUG]: finish: modules-config/config-http_scripts: SUCCESS: config-http_scripts ran successfully

https://get.docker.comのログ(最大10行)

$ grep "Executing docker install script" /var/log/cloud-init-output.log -A10
# Executing docker install script, commit: 4f282167c425347a931ccfd95cc91fab041d414f
+ sh -c apt-get update -qq >/dev/null
+ sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -qq apt-transport-https ca-certificates curl >/dev/null
+ sh -c mkdir -p /etc/apt/keyrings && chmod -R 0755 /etc/apt/keyrings
+ sh -c curl -fsSL "https://download.docker.com/linux/ubuntu/gpg" | gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg
+ sh -c chmod a+r /etc/apt/keyrings/docker.gpg
+ sh -c echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu focal stable" > /etc/apt/sources.list.d/docker.list
+ sh -c apt-get update -qq >/dev/null
+ sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends docker-ce docker-ce-cli containerd.io docker-compose-plugin docker-scan-plugin >/dev/null
+ version_gte 20.10
+ [ -z  ]

https://get.k0s.shのログ

$ grep "k0s" /var/log/cloud-init-output.log
Downloading k0s from URL: https://github.com/k0sproject/k0s/releases/download/v1.25.4+k0s.0/k0s-v1.25.4+k0s.0-amd64
k0s is now executable in /usr/local/bin

dockerとk0sがインストールされていることを確認できました。

$ sudo docker version
Client: Docker Engine - Community
 Version:           20.10.22
 API version:       1.41
 Go version:        go1.18.9
 Git commit:        3a2c30b
 Built:             Thu Dec 15 22:28:08 2022
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.22
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.18.9
  Git commit:       42c8b31
  Built:            Thu Dec 15 22:25:58 2022
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.14
  GitCommit:        9ba4b250366a5ddde94bb7c9d1def331423aa323
 runc:
  Version:          1.1.4
  GitCommit:        v1.1.4-0-g5fd4c4d
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

$ k0s version
v1.25.4+k0s.0

ちなみに... ubuntuユーザーが初期化されるわけではないのでdebファイルが残っていたり、最初の起動時に指定した公開鍵が.ssh/authorized_keysに残っていたりします。
マイアーカイブ化する前にubuntuユーザーは削除するのが良いのかなと思います。

$ ls
cloud-init_22.4-51-g75953781-1~bddeb_all.deb
$ head -n1 .ssh/authorized_keys
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQD0x++fAvsnjj5/BviVPDSEEg9fxTvvMhFRVqCNG0uwNP+sQ6Ng9joTcQONFeTRDIK/VwYAhQvhSnQztu3ybp/0vLFXvbBUtp1sQLZiI5l/aUh+Nfo+Kq26T0cBtXVOp4Leyug3nYK+T/kFLU2u/utbzJbD9bhn/1+gUdY2VLo28bSdz+9jlXSo9YzisETZm2PuAUH2cMc1dk1O0UpmvuPKGX+iEiiJHsb1dWj7HfGnJquYHJ2hQSXVpsR2o0hsGapBFbpiq/MHqzSZA71OxHIujqlrnQ1SVQsW7S0w5oNZVoMK1IYw055//iytNjLc9f8QpcWXG9sY4sOySbFUUiNg7j1aoDjWxpPA0lvLPOXeQNZXT+z7zqd52fz+QrsjwNvGErod3UP0WFPy5/tJrnmeZxbNSVmfX+O8npswGs7Y4Nb1AGgNpKK2vTkaCFVHgfwJ5oLTYdbh3AFrC2Do3Zomk2XHxXFxxZRHAixKMe/79Nxi2F95/6EEDMK+89ReeI8= ubuntu@vm

さいごに

今回は、urlを指定するとスクリプトをダウンロードして実行するcloud-initのモジュールを作成する流れを紹介させていただきました。
cloud-initでこんなことできたらいいのに!というアイデアをお持ちの方はぜひモジュール自作をしてみてはいかがでしょうか。
もしこの記事がその助けになれば幸いです。

余談ですが、このhttp_scriptsに実行時に環境変数も指定できる機能を追加しcanonical/cloud-initにPRを出してみました。マージされるかはわかりませんが、もしマージされれば各ディストリビューションが配布しているcloudimgに標準で取り込まれることになります。その際はぜひ使っていただけたらなと思います。

参考

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0