見出し画像

WSL上にLinux OSをインストールして初期設定するAnsibleカスタムモジュールを作る


はじめに


こんにちは。株式会社SHIFT ITソリューション部の水谷です。

皆さんはWindows上でLinuxを使っていますでしょうか?
仕事もプライベートも、Windows上のアプリとWebアプリだけ行っている方がほとんどかと思いますが、私のようにITインフラ系の仕事をしていると、どうしてもLinux系OSを使わざるを得ない場面があったりします。メインはWindowsで、一部Linux系OSも使う、という私のようなエンジニアにとって、Windows上で完全なLinux OSを動かせるWSL(Windows Subsystem for Linux)はとても重宝しますよね。

さて、WSL上にUbuntuなどのLinux系OSをインストールする方法としては、Microsoft Storeから検索してインストールする方法が良く使われていますが、コマンドラインからも行えます。ということは、「自動化も可能」ということになります。

しかし、Ansible Galaxyを見たところ、WSLでLinux系OSをインストールするコレクションは存在しないようでしたので、それなら自分で作ってみようと思い立ちました。

そこで、今回はWindowsマシンに対してUbuntuなどのWSL用LinuxディストリビューションをインストールするAnsibleカスタムモジュールを作っていく様子を書いてみたいと思います。

以前Excelの読み書きを行うカスタムモジュールを作りましたが、その時に作ったモジュールはLinux上で動作するものでした(こちらの記事 )。今回はWindows上で動作するモジュールということで、作り方も大きく異なります。そのあたりの差も見ながら進めていきたいと思います。

※本記事で作成したモジュールを Ansible Galaxy でコレクションの形で公開していますので、是非覗いてみてください。

モジュール仕様の検討


まずは、どんなモジュールを作るか簡単に検討していきます。

まず、Mustでほしい機能としては、Ubuntu-24.04などのディストリビューションのインストールとアンインストール機能です(当然ですね)。とりあえずこの機能を実装した状態が、MVP(minimum viable product)になるかと思います。

あと、インストール時にデフォルトユーザーを作成したり、初期設定コマンドを実行する機能も欲しいので、これもMust機能にします。

Good to haveな機能としては、インストール済み、およびインストール可能なディストリビューションのリストを取得する機能が挙げられますね。

あと、ディストリビューション単位で、停止や登録解除の作業はよく行うので、これも入れておきます。

それから、WSL自体が時々バージョンアップされているので、WSL自体のアップデート機能、それからWSLのシャットダウンも欲しいところです。

あとは、バーチャルHDDのExport/Import機能もありますね。

最終的には上記すべて実装しようと思いますが、本記事ではMustとして挙げた機能と、インストールされているディストリビューションのリストを取得する機能、ディストリビューションの停止、登録解除までを実装してみようと思います。

なお、WSLディストリビューションのインストールはユーザー単位で行われ、いわゆる"All Users"に対するインストールはできません。ですので、ここで作成するモジュールは、ディストリビューションをインストールしたいユーザーで接続されている(ansible_userが、ディストリビューションをインストールしたいユーザーとなっている)ことを前提にしたいと思います。"runas"を使うなどで、対処は可能かもしれませんが、検証などちょっと手間がかかりそうなので……。

また、このモジュールはwsl.exe(WSLに対して各種操作を行うコマンドラインツール)のラッパー的なものになるのですが、そのwsl.exeには2種類のものが存在します。1つは、Windowsに初めから入っているもの(C:\Windows\System32\wsl.exe)で、もう1つはMicrosoft Storeからインストール可能な、いわゆる"Store version of WSL"です。自動化を行うにあたっての一番大きな問題として、Windowに入っている方のwsl.exeはリモートセッション(正しくはsession 0)では動作しないことが挙げられます。つまり、Windowsにデフォルトで入っているwsl.exeしかインストールされていないリモートホストに対してこのモジュールが使用できません。

対策としては、このモジュールを使用する前に wsl.exe --update を実行れば良いのですが、そのwsl.exeの実行がリモートではできないので、リモートから行うならwinget.exeコマンドを使ってMicrosoft Storeから"Store version of WSL"をインストールする、あるいはwin_chocolateyモジュールでインストールする方法が考えられます。せっかくなので、(1つのコレクションで完結できるよう)前者を行うモジュールを別途作ろうと考えているところです(うまく作れればGitHubおよびAnsible Galaxyで公開しようと思っています)。

コレクションの準備


今回も、モジュール単体で作るのではなく、コレクションとして作りたいと思います。コレクションは、モジュールだけでなく、ロールやフィルタープラグインなど、多くの種類のAnsibleのパーツが格納できるパッケージのようなもので、Ansible Galaxyで共有することができます。

ということで、まずは空のコレクションを1つ作ります。

今回は C:\Projects\Note\wsl というディレクトリをプロジェクトディレクトリとし、そこにPlaybookやインベントリファイルを作成し、コレクションは collections\ansible_collections というディレクトリを作成して、その下に作成することにします。namespaceは yumizu11.wsl としました。

以下はWSL上のUbuntuでディレクトリ作成とコレクションのひな形作成のコマンドです。

$ ansible-galaxy collection init yumizu11.wsl --init-path collections/ansible_collections
- Collection test.excel was created successfully

Windowsで動作するモジュールの特徴


Ansibleのモジュールの多くは、そのコードの一部が対象ホスト内に送り込まれて実行されます。Linux系OSがホストの場合、送り込まれるコードはPythonで書かれたものなのですが、WindowsやWindows Serverには、Pythonはデフォルトでは入っていません。そこで、Windows系OSに送り込まれるコードはPowershellで書かれたコードとなります。ですので、Windowsを操作するモジュールもPowershellで書くことになります。

パラメーターの受け取り方やリターンする値は、Pythonで書かれたLinux系OS用モジュールと似ているので、Powershellでの開発経験があればそれほど違和感なくWindow用のモジュールも作ることができます。

パラメーターの受け取りや、Ansibleに実行結果を返すのは、Ansible.Basic.AnsibleModuleクラスを通して行います。

$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)

このように $module 変数を作り、これを通してパラメーターにアクセスしたり、$module.FailJson()とFailJsonメソッドを呼び出すことでモジュールの実行を失敗終了させたりします。

あと、Windows用モジュールの開発で特徴的なのが、モジュールのドキュメントや使用例はPythonの変数として書くことです。機能自体はPowershellで書いて、ドキュメントはLinux系OS用のモジュールと同じようにPythonファイルに記述する、ということになります。

ディストリビューションを操作するモジュールの実装方針


さて、今回のモジュールをどのような実装にするか考えてみたのですが、install_xxxとか、uninstall_xxxのような命令的なモジュールではなく、既存のインストール系モジュール(yumモジュールなど)にならって、stateパラメーターで状態を記述するようなモジュールとして実装したいと思います。

モジュール名は、distribution(FQCNは yumizu11.wsl.distribution)とし、stateの種類は以下のように定義することにします。

distributionモジュールの実装


PowershellによるWindows操作モジュールの開発は、こちらのドキュメント に沿って行います。

まず、$moduleインスタンスの作成と、モジュールパラメーターの受け取りは、以下のように作りました(現時点ではcheck modeには非対応)。

$spec = @{
    options = @{
        name = @{ type = "str"; required = $false }
        state = @{ type = "str"; choices = "absent", "present", "installed", "query", "reset", "terminated", "unregistered"; default = "present" }
        is_default = @{ type = "bool"; default = $false}
        default_user = @{ type = "str"; required = $false }
        run_cmd = @{ type = "list"; required = $false }
    }
    # supports_check_mode = $true
}

$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)

$name = $module.Params.name
$state = $module.Params.state
$is_default = $module.Params.is_default
$default_user = $module.Params.default_user
$run_cmd = $module.Params.run_cmd

モジュールパラメーターは以下の通りです。

今後増えるかもしれないですが、一旦これだけで進めます。

さて、このモジュールでは主にwsl.exeを実行してディストリビューションのインストールなどを行いますので、wsl.exeや任意の実行ファイルを実行する関数を作っておきます。

※Run-Commandというコマンドが使用できますが、stdoutとstderrにゴミ(\u0000)がたくさん出力されるため、それは使わずに独自に実装しています。

function Invoke-Command {
    param([Parameter(Mandatory=$true)][string] $Command, [string] $Arg = "")
    $pinfo = New-Object System.Diagnostics.ProcessStartInfo
    $pinfo.FileName = $Command
    $pinfo.RedirectStandardError = $true
    $pinfo.RedirectStandardOutput = $true
    $pinfo.StandardOutputEncoding = [System.Text.Encoding]::Unicode
    $pinfo.StandardErrorEncoding = [System.Text.Encoding]::Unicode
    $pinfo.UseShellExecute = $false
    $pinfo.CreateNoWindow = $true
    $pinfo.Arguments = $Arg
    $p = New-Object System.Diagnostics.Process
    $p.StartInfo = $pinfo
    $p.Start() | Out-Null
    $p.WaitForExit()
    $stdout = $p.StandardOutput.ReadToEnd()
    $stderr = $p.StandardError.ReadToEnd()

    return @{"rc" = $($p.ExitCode); "stdout" = $($stdout); "stderr" = $($stderr)}
}

function Invoke-WSL {
    param([string] $Arg = "")
    return (Invoke-Command -Command "`"$wsl_path`"" -Arg $Arg)
}

$wsl_path = Join-Path -Path Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) -ChildPath "\WSL\wsl.exe"
if ((Test-Path -Path $wsl_path) -eq $false) {
    $module.FailJson("The store version of WSL is not installed.")
}

Invoke-Commandは任意の実行ファイルを実行し、ExitCodeと標準出力、および標準エラーを返す関数で、Invoke-WSLはInvoke-Commandを使って wsl.exe を実行する関数です。

さて、ディストリビューションのインストール、アンインストール、停止、登録解除を行う際に、そのディストリビューションがインストール済みかどうかを確認するために必要となります。また、stateにqueryが指定された際にもこの情報を返したいので、関数として作っておきます。

function Get-InstalledDistro {
    $result = Invoke-WSL -Arg '--list -v'
    $default_distro = ""
    $installed_distro = @{}
    if ($result.rc -eq 0) {
        $lines = $result.stdout.Split("`r`n")
        (1..($lines.length - 1)) | ForEach-Object {
            if ($lines[$_].length -ge 2) {
                $lines[$_].SubString(2) -match '(\S+)\s+(\S+)\s+(\S+)'
                $distro_name = $Matches[1]
                $installed_distro.Add($distro_name, @{ "state" = $Matches[2]; "version" = $Matches[3] })
                if ($lines[$_][0] -eq '*') {
                    $default_distro = $distro_name
                }
            }
        }
    }

    return @{"rc" = $result.rc; "installed_distro" = $installed_distro; "default_distro" = $default_distro }
}

この関数をモジュールの冒頭で呼び出しておき、結果を$installed_distro変数と$default_distro変数に入れておきます。

$current_distro = Get-InstalledDistro
if ($current_distro.rc -ne 0) {
    $module.FailJson("Failed to get installed distro list. rc = $($result.rc)")
}
$installed_distro = $current_distro.installed_distro
$default_distro = $current_distro.default_distro

state: terminated

では、準備ができましたので、state毎の処理を実装していきましょう。まずは一番簡単な terminated からです。

if ($state -eq "terminated") {
    if ($name -in $installed_distro.Keys() -and $installed_distro[$name].state -eq "Running") {
        $result = Invoke-WSL -Arg "--terminate -d $name"
        $module.Result.stdout = $result.stdout
        $module.Result.stderr = $result.stderr
        if($result.rc -eq 0) {
            $module.Result.changed = $true
        } else {
            $module.FailJson("Failed to terminate '$name'.")
        }
    }
    $current_distro = Get-InstalledDistro
    if ($current_distro.rc -eq 0) {
        $module.Result.installed_distro = $current_distro.installed_distro
        $module.Result.default_distro = $current_distro.default_distro
    }
}

冪等性を考慮し、nameパラメーターで渡されたディストリビューションが、インストール済みで、かつ実行中であるときだけ、wsl.exe --terminateコマンドを実行して停止します。

それから、ディストリビューションの状態を再取得して、停止作業が終わった状態の最新状態をモジュールの戻り値に入れています。

state: unregistered

続いて、登録解除の実装を行います。

これは、stateがunregisteredの時だけでなく、resetやabsentでも一旦登録解除を行いますので、stateがこの3つのうちのどれかであれば実行します。

if ($state -eq "unregistered" -or $state -eq "reset" -or $state -eq "absent") {
    if ($name -in $installed_distro) {
        $result = Invoke-WSL -Arg "--unregister -d $name"
        $module.Result.stdout = $result.stdout
        $module.Result.stderr = $result.stderr
        if($result.rc -eq 0) {
            $module.Result.changed = $true
        } else {
            $module.FailJson("Failed to unregister '$name'.")
        }
    }
    $current_distro = Get-InstalledDistro
    if ($current_distro.rc -eq 0) {
        $module.Result.installed_distro = $current_distro.installed_distro
        $module.Result.default_distro = $current_distro.default_distro
    }
}

やっている内容はterminateとほぼ同じですね。

state: present, installed, reset

さて、ディストリビューションのインストール系の処理ですが、stateがpresent、installed、およびresetの時をまとめて実装します。

まずは、パッケージのインストール部分です。

if ($state -eq "present" -or $state -eq "installed" -or $state -eq "reset") {
    if ($name -notin $installed_distro.Keys()) {
        $result = Invoke-WSL -Arg "--install $name -n"
        $module.Result.stdout = $result.stdout
        $module.Result.stderr = $result.stderr
        if($result.rc -eq 0) {
           $module.Result.changed = $true
        } else {
            $module.FailJson("Failed to install '$name'.")
        }
    }

wsl.exeを--installオプションと、インストールするディストリビューション名、そして-nオプションをつけて実行しています。wsl.exeの--installオプションは、ディストリビューションをインストールした後、自動的にそのディストリビューションを起動しますが、これでは勝手にユーザー作成フェイズに進んでしまいます(ユーザーからのキーボード入力が求められるが、Ansibleからは入力できないのでそこから進めなくなる)ので、-nを付けて、起動しないようにしています。

続いて、defaultパラメーターにtrueが指定されている場合は、インストールしたディストリビューションをデフォルトディストリビューションに指定します。

   if (($state -eq "present" -or $state -eq "installed") -and $is_default -and $name -ne $default_distro) {
        $result = Invoke-WSL -Arg "--set-default $name"
        if($result.rc -eq 0) {
            $module.Result.changed = $true
        } else {
            $module.FailJson("Failed to set '$name' default distro.")
        }
    }

さて、デフォルトユーザーの作成と初期化コマンド実行ですが、WSL自体にはそのような機能はありません。が、Cloud-init を使うことで(すべてのディストリビューションではありませんが)実現できます。

C:\Users<ユーザー名> フォルダの下に .cloud-initフォルダを作成しておき、そこに<ディストリビューション名>.user-dataというテキストファイルを作ります。そして以下の内容を書いておき、(Ubuntu 24.04の場合は)ubuntu2404.exe install --rootを実行すれば良いようです(こちらのサイト に記述があります)。

 #cloud -config
users:
- name: taro
  groups: [adm,dialout,cdrom,floppy,sudo,audio,dip,video,plugdev,netdev]
  sudo: ALL=(ALL) NOPASSWD:ALL
  shell: /bin/bash

write_files:
- path: /etc/wsl.conf
  append: true
  content: |
    [user]
    default=taro

runcmd:
- apt install python3-pip -y

Cloud-initが使用できるのはsystemdが有効なディストリビューションのみとなっています。各種Ubuntuは大丈夫ですが、それ以外はサポートしているディストリビューションは今のところ無いように見えます。そこで、今回は(簡易的な実装となりますが)ディストリビューション名が"Ubuntu"で始まっている場合のみこれを実行することにします。

ということで、以下のように実装しました。

if ($state -eq "installed" -and ($default_user -ne "" -or $run_cmd.length -ne 0)) {
        if ($name.StartsWith("Ubuntu")) {
            $userprofile_dir = [environment]::getfolderpath("UserProfile")
            $conf_folder = Join-Path -Path $userprofile_dir -ChildPath ".cloud-init"
            if ((Test-Path $conf_folder) -eq $false) {
                New-Item -Path $conf_folder -ItemType Directory | Out-Null
            }
            $contents = @()
            $contents += "#cloud-config"
            if ($default_user -ne "") {
                $contents += "users:"
                $contents += "- name: $default_user"
                $contents += "  groups: [adm,dialout,cdrom,floppy,sudo,audio,dip,video,plugdev,netdev]"
                $contents += "  sudo: ALL=(ALL) NOPASSWD:ALL"
                $contents += "  shell: /bin/bash"
                $contents += "write_files:"
                $contents += "- path: /etc/wsl.conf"
                $contents += "  append: true"
                $contents += "  content: |"
                $contents += "    [user]"
                $contents += "    default=$default_user"
            }
            if ($run_cmd.Count) {
                $contents += "runcmd:"
                $run_cmd | ForEach-Object {
                    $contents += "- $_"
                }
            }
            $fs = New-Object StreamWriter((Join-Path -Path $conf_folder -ChildPath "$name.user-data"), $false)
            $contents | ForEach-Object {
                $fs.WriteLine($_)
            }
            $fs.Close()

            $distro_exe = $name.Replace("-", "").Replace(".", "").Replace("_", "") + ".exe"
            $result = Invoke-Command -Command $distro_exe -Arg "install --root"
            if($result.rc -ne 0) {
                $module.FailJson("Failed to run $distro_exe with Cloud-init conf file.")
            } else {
                $result = Invoke-Command -Command $distro_exe -Arg "run cloud-init status --wait"
                $module.Result.cloudinit_rc = $result.rc
                $module.Result.cloudinit_stdout = $result.stdout
                $module.Result.cloudinit_stderr = $result.stderr
                Invoke-WSL -Arg "-terminate $name"
            }
        } else {
            $module.Warn("Because this distro does not support systemd, cloud-init cannot be applied.")
        }
    }

最後にstateがabsentの場合の処理ですが、こちらはディストリビューションの登録解除だけではなく、パッケージのアンインストールも行います。
インストールされた(正確にはインストールされて1回以上起動された)ディストリビューションは、レジストリのHKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Lxssに登録されます。

この中でDistributionNameがアンインストールしたいディストリビューション名のキーを探し、そこにあるPackageFamilyNameを取得します。このPackageFamilyNameは、"_"(アンダーバー)が必ず入っているのですが、その手前までがPackageNameになります。これをRemove-AppxPackageコマンドに渡せば、そのパッケージがアンインストールされることになります。

流れとしては、Pakcage名を取得し、unregister(この時点でレジストリは削除される)、そしてパッケージのアンインストールとすればOKですね。

if ($state -eq "absent") {
    if ($name -in $installed_distro.Keys) {
        $reg = Get-ChildItem "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Lxss" | Get-ItemProperty | Where-Object { $_.DistributionName -eq $name }
        if ($reg) {
            $familyname = $reg.PackageFamilyName
            $packagename = $familyname.SubString(0, $familyname.LastIndexOf("_"))
            $package = Get-AppxPackage -name $packagename

            $result = Invoke-WSL -Arg "--unregister $name"
            $module.Result.stdout = $result.stdout
            $module.Result.stderr = $result.stderr
            if($result.rc -eq 0) {
                $module.Result.changed = $true
            } else {
                $module.FailJson("Failed to unregister '$name'.")
            }
    
            if ($package) {
                Remove-AppxPackage -Package $package
            } else {
                $module.Warn("Appx package could not be removed.")
            }
        } else {
            $module.Warn("Appx package could not be removed, because it was never launched.")
        }
    }
    $current_distro = Get-InstalledDistro
    if ($current_distro.rc -eq 0) {
        $module.Result.installed_distro = $current_distro.installed_distro
        $module.Result.default_distro = $current_distro.default_distro
    }
}

以上で実装は終わりです。ソースコードがそこそこ長くなってしまいましたので、コード全体はこちらには載せませんが、GitHub で公開していますので、ご興味がありましたら覗いてみてください。

テスト実行


では軽く実行してみましょう。

まずは、stateをqueryにして、現在のディストリビューションのリストを取得してみます。

Playbookはこんな感じです。

---
- hosts: mypc
  gather_facts: false
  tasks:
    - name: Query test
      yumizu11.wsl.distribution:
        state: query
      register: query_result

    - name: Debug - show query_result
      ansible.builtin.debug:
        var: query_result

実行結果は以下のようになりました。

$ ansible-playbook -i inventory site.yml

PLAY [mypc] *************************************************************************************************

TASK [Query test] *******************************************************************************************
ok: [mypc]

TASK [Debug - show query_result] ****************************************************************************
ok: [mypc] => {
    "query_result": {
        "changed": false,
        "default_distro": "kali-linux",
        "failed": false,
        "installed_distro": {
            "Ubuntu-22.04": {
                "state": "Running",
                "version": "2"
            },
            "kali-linux": {
                "state": "Stopped",
                "version": "2"
            }
        }
    }
}

PLAY RECAP **************************************************************************************************
mypc                       : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

戻り値のinstalled_distroに、Ubuntu-22.04とkali-linuxがリストアップされ、それぞれの状態とバージョンの情報も収集されていますね。

また、デフォルトがkali-linuxになっていることもわかります。

続いてUbuntu 24.04をインストールしてみます。また、mizutaniというユーザーを作成し、これをデフォルトユーザーにし、またAnsibleの最新版をインストールします。

Playbookはこちら。

---
- hosts: mypc
  gather_facts: false
  tasks:
    - name: Install test
      yumizu11.wsl.distribution:
        name: 'Ubuntu-24.04'
        is_default: true
        default_user: mizutani
        run_cmd:
          - add-apt-repository ppa:ansible/ansible -y
          - apt update
          - apt upgrade -y
          - apt install ansible -y
        state: installed
      register: install_result

    - name: Debug - show install_result
      ansible.builtin.debug:
        var: install_result
$ ansible-playbook -i inventory site.yml

PLAY [mypc] *************************************************************************************************

TASK [Install test] *****************************************************************************************
[WARNING]: Module invocation had junk after the JSON data:  System.Collections.Hashtable
changed: [mypc]

TASK [Debug - show install_result] **************************************************************************
ok: [mypc] => {
    "install_result": {
        "changed": true,
        "cloudinit_rc": 0,
        "default_distro": "Ubuntu-24.04",
        "failed": false,
        "installed_distro": {
            "Ubuntu-22.04": {
                "state": "Running",
                "version": "2"
            },
            "Ubuntu-24.04": {
                "state": "Stopped",
                "version": "2"
            },
            "kali-linux": {
                "state": "Stopped",
                "version": "2"
            }
        },
        "stderr": "",
        "stderr_lines": [],
        "stdout": "インストール中: Ubuntu 24.04 LTS\r\nUbuntu 24.04 LTS がインストールされました。\r\nこの操 作を正しく終了しました。 \r\n",
        "stdout_lines": [
            "インストール中: Ubuntu 24.04 LTS",
            "Ubuntu 24.04 LTS がインストールされました。",
            "この操作を正しく終了しました。 "
        ]
    }
}

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

無事できたようです。default_distroも変わってますね。

Ubuntu-24.04を起動してみます。

期待通りmizutaniというユーザーが作られていて、Ansibleのインストールもできていますね。

最後はアンインストールを試してみましょう。

---
- hosts: mypc
  gather_facts: false
  tasks:
    - name: Uninstall test
      yumizu11.wsl.distribution:
        name: 'Ubuntu-24.04'
        state: absent
      register: uninstall_result

    - name: Debug - show uninstall_result
      ansible.builtin.debug:
        var: uninstall_result

実行結果です。

$ ansible-playbook -i inventory site.yml

PLAY [mypc] *************************************************************************************************

TASK [Uninstall test] ***************************************************************************************
changed: [mypc]

TASK [Debug - show uninstall_result] ************************************************************************
ok: [mypc] => {
    "uninstall_result": {
        "changed": true,
        "default_distro": "kali-linux",
        "failed": false,
        "installed_distro": {
            "Ubuntu-22.04": {
                "state": "Running",
                "version": "2"
            },
            "kali-linux": {
                "state": "Stopped",
                "version": "2"
            }
        },
        "stderr": "",
        "stderr_lines": [],
        "stdout": "この操作を正しく終了しました。 \r\n",
        "stdout_lines": [
            "この操作を正しく終了しました。 "
        ]
    }
}

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

installed_distroからUbuntu-24.04消えていることが確認できました。

最後に


軽い気持ちで作り始めたら、いろいろ考えることも多くありちょっと時間がかかりましたが、無事動作するところまで進めることができました。

まだ、Good to haveな機能の実装ができてないのと、ドキュメントが書けてないですが、近いうちにこのあたりも進めて、Ansible Galaxyで公開できればいいなと思っています。

Ansibleのモジュール開発は、多少慣れが必要なところはありますが、難易度はそれほど高くないですし、最小限の機能で作って動作確認し、徐々に肉付けしていく感じで作っていく開発手法が取れるので、日曜大工的なプログラミングとしてはうってつけなのではないか、と個人的には思っています。

Ansibleをよく使われていてプログラミングが趣味の方は、空いた時間で何か1つモジュールの実装を経験してみてはいかがでしょうか?

★公式ブロガー水谷の執筆記事一覧


執筆者プロフィール:水谷 裕一
大手外資系IT企業で15年間テストエンジニアとして、多数のプロジェクトでテストの自動化作業を経験。その後画像処理系ベンチャーを経てSHIFTに入社。
SHIFTグループ会社「RGA」および「システムアイ」に出向し、インフラ構築の自動化やCI/CD、コンテナ関連の業務に従事した後、2024年3月よりSHIFTのITソリューション部に配属。
ここ数年はAnsibleにかなりハマってます。


IaC支援サービスのご紹介

SHIFTではTerraformやCDKを使ったクラウドインフラ構築の自動化や、Ansibleを使ったサーバOSの設定自動化や構成管理のご支援も行っております。ご依頼・ご相談は下記リンクからお願いします。

お問合せはお気軽に
https://service.shiftinc.jp/contact/

SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/

SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/

SHIFTの導入事例
https://service.shiftinc.jp/case/

お役立ち資料はこちら
https://service.shiftinc.jp/resources/

SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/

PHOTO:UnsplashVivid Firefly