見出し画像

ラズパイにAWXとGitLabをインストールするPlaybookを作る


ITソリューション部の水谷です。

先日弊社のアドベントカレンダーに「ラズパイにAWXとGitLabを入れて快適な自宅Playbook環境を作る」というタイトルの記事 を投稿しました。

その記事では、附属品込みで2万円程度で購入した Raspberry Pi 5(メモリ8GB)にAWXとGitLab CEをインストールして、自宅用Playbook実行、ソースコード管理、CI/CD実行環境を手動で構築した模様をお伝えしました。

今回はその記事の続編として、すべての工程をPlaybook化してみたいと思います。

なお、このPlaybookは本記事執筆時点では期待通り動作していますが、今後(依存しているライブラリがバージョンアップした際に互換性がなくなる等の理由で)動作しなくなる可能性がありますので、その点はご了承ください。

※PlaybookはGitHubからCloneすることができます。

Playbook化する理由


本題に入る前に、少し「なぜPlaybook化しようと思ったのか」について書いておきたいと思います。

かなり個人的な考えも含まれますが、Playbook化した理由は以下のようなものです。

  • 今後何回か(設定を少し変えて)構築する可能性があるので、自動化しておきたい

  • Playbookを見れば、後からどんな設定を行ったのかが分かるので、仕様書代わりに残しておきたい

  • ちょうどいいサイズと難易度なので、Playbookを書く良い練習になる

  • 自動化を通じてAWXやGitLabを少し勉強できる

最後の点については、特にGitLabのパーソナルアクセストークンを取得(作成?)するところや、RunnerをGitLabに登録する方法などが学べました。

ちょっと時間はかかりますが、何かを構築する際にPlaybookを書くことには、いろいろメリットがあるのではないか、と感じています。

メインPlaybook


さて、作成したPlaybookを見ていきましょう。

このPlaybookでやりたいことを列挙すると以下になります。

  • AWXのインストール

  • GitLabのインストール

  • GitLab Runnerのインストール

  • AWX/GitLab一括起動スクリプトの作成

  • 連携サンプルの作成

本来ならばそれぞれをRoleとして実行していくのが良いかと思いますが、これは自分用のPlaybookということで、ちょっと楽をして(?)それぞれをタスクファイルとして作り、Playbookはそれらをincludeする形にしたいと思います。

ということでPlaybookはこんな感じです。varsのところでいろいろ設定できるようにしています。

---
- name: Setup Gitlab and AWX on Raspberry Pi
  hosts: all
  vars:
    awx_source_path: ~/awx  # AWXリポジトリのクローン先ディレクトリ
    awx_version: 24.6.1  # インストールするAWXのバージョン
    awx_password_file: ~/awx_admin_password  # AWXのadminパスワードの保管先ファイル
    gitlab_version: 17.5.1-ce.0  # インストールするGitLabのバージョン
    gitlab_password_file: ~/gitlab_root_password  # GitLabのrootパスワードの保管先ファイル
    gitlab_runner_image: gitlab/gitlab-runner:latest  # GitLab Runnerのコンテナイメージ
    gitlab_runner_container_image: ghcr.io/ansible/community-ansible-dev-tools:latest  # Runner内で実行するコンテナイメージ
    gitlab_runner_name: Arm-docker-runner  # Runnerの名前
    gitlab_access_token: hQT6ms9MKAyEZtfag7yD  # GitLabのrootユーザー用パーソナルアクセストークン(任意の20文字)
    restart_alyways: true  # ラズパイ起動時にすべてのコンテナを起動するか否か
  tasks:
    - name: Include prepare tasks
      ansible.builtin.include_tasks: prepare.yml

    - name: Include awx tasks
      ansible.builtin.include_tasks: awx.yml

    - name: Include gitlab tasks
      ansible.builtin.include_tasks: gitlab.yml

    - name: Include runner tasks
      ansible.builtin.include_tasks: runner.yml

    - name: Include launch_script tasks
      ansible.builtin.include_tasks: launch_script.yml

事前準備タスク


事前準備タスク(prepare.yml)では以下を行います。

  • apt updateとapt upgradeでOSを最新化する

  • 必要なソフトウェア(ansible-core、docker.io、docker-compose、docker-buildxそしてmake)をインストールする

  • sudoなしでdockerコマンドの実行ができるようにする

  • dockerサービスの起動とenable

  • docker buildxのインストール

実装は以下の通り。さほど難しくないかと思いますので、コードのみ書いておきます。

---
- name: Upgrade the OS
  ansible.builtin.apt:
    upgrade: yes
    update_cache: yes
  become: true
  
- name: Install required software
  ansible.builtin.apt:
    name:
      - ansible-core
      - docker.io
      - docker-compose
      - docker-buildx
      - make
  become: true

- name: Add user to docker group
  ansible.builtin.user:
    name: "{{ ansible_user }}"
    groups:
      - docker
    append: true
  become: true

- name: Change owner of docker.sock to the user
  ansible.builtin.file:
    path: /var/run/docker.sock
    owner: "{{ ansible_user }}"
  become: true

- name: Enable and start docker service
  ansible.builtin.service:
    name: docker
    enabled: true
    state: started
  become: true

- name: Install docker buildx
  ansible.builtin.command: docker buildx install

AWXのインストールの自動化


続いてAWXをインストールする awx.yml ですが、基本的に前記事で書いた流れをそのままPlaybook化しています。

1つずつ見ていきましょう。まずはAWXリポジトリのクローンです。

- name: Remove old AWX repository if exits
  ansible.builtin.file:
    path: "{{ awx_source_path }}"
    state: absent

- name: Clone AWX repository
  ansible.builtin.git:
    repo: https://github.com/ansible/awx.git
    version: "{{ awx_version }}"
    dest: "{{ awx_source_path }}"

クローン先ディレクトリは、awx_source_path変数で、インストールするAWXのバージョンはawx_source_path変数で与えられることを想定しています。

次は make docker-compose-build の実行です。

今回インストールするAWXのバージョン24.6.1では、django-ansible-base と OpenSSLのバージョンを変更する必要があるため、その処理も入れています(今後のバージョンでは不要になるかもしれないので、このバージョンの場合だけ実行するようにしました)。

- name: Apply some changes if AWX version is 24.6.1
  block:
    - name: Update requirement_git.txt to use stable django-ansible-base
      ansible.builtin.lineinfile:
        path: "{{ awx_source_path }}/requirements/requirements_git.txt"
        regex: '^django'
        line: 'django-ansible-base @ git+https://github.com/ansible/django-ansible-base@2024.7.17#egg=django-ansible-base[rest_filters,jwt_consumer,resource_registry,rbac]'

    - name: Update openssl version (1/2)
      ansible.builtin.lineinfile:
        path: "{{ awx_source_path }}/tools/ansible/roles/dockerfile/templates/Dockerfile.j2"
        regex: 'openssl-3.0.7'
        line: '    openssl-3.2.2 \'

    - name: Update openssl version (2/2)
      ansible.builtin.lineinfile:
        path: "{{ awx_source_path }}/tools/ansible/roles/dockerfile/templates/Dockerfile.j2"
        regex: 'openssl-3.0.7'
        line: '    openssl-3.2.2 \'
  when:
    - awx_version == '24.6.1'

- name: Run make docker-compose-build
  ansible.builtin.command:
    cmd: 'make docker-compose-build'
    chdir: "{{ awx_source_path }}/"

次は make docker-composeですが、これを実行するとAWXの起動まで行ってしまうので、Makefile を少し変更して、docker-compose.ymlの生成だけを行います。

- name: Update Makefile not to launch AWX automatically
  ansible.builtin.lineinfile:
    path: "{{ awx_source_path }}/Makefile"
    regex: '^\s*\$\(MAKE\) docker-compose-up'
    line: '#	$(MAKE) docker-compose-up'

- name: Run make docker-compose
  ansible.builtin.command:
    cmd: 'make docker-compose'
    chdir: "{{ awx_source_path }}/"

これでdocker-compose.ymlができたはずです。ただし、このまま docker-compose で起動するとrsyslog.d関連のエラーがでますので、以下を実行して回避します。

※詳しくはこちらを参照:https://github.com/ansible/awx/issues/14259

- name: Workaround for rsyslogd problem
  ansible.builtin.command: "{{ item }}"
  loop:
    - ln -s /etc/apparmor.d/usr.sbin.rsyslogd /etc/apparmor.d/disable/
    - apparmor_parser -R /etc/apparmor.d/usr.sbin.rsyslogd
  become: true

生成された docker-compose.yml には restart の設定は書かれていないので、ラズパイ起動時にAWXを起動したい場合は、各サービスに restart: always を追加します。

※Playbook内で定義している変数 restart_always が true の時のみ実行するようにしています。

- name: Update docker-compose.yml to launch automatically
  ansible.builtin.lineinfile:
    path: "{{ awx_source_path }}/tools/docker-compose/_sources/docker-compose.yml"
    insertafter: "{{ item.regexp }}"
    line: "{{ item.line }}"
  loop:
    - regexp: 'awx_1:$'
      line: '    restart: always  # awx'
    - regexp: 'redis_1:$'
      line: '    restart: always  # redis'
    - regexp: 'postgres:$'
      line: '    restart: always  # postgres'
  when:
    - restart_always

続いて、docker-compose を実行するコマンドを awx_launch_command 変数に入れて、それを実行します。

- name: Generate AWX launch command
  ansible.builtin.set_fact:
    awx_launch_command: 
      "docker-compose -f {{ awx_source_path }}/tools/docker-compose/_sources/docker-compose.yml up -d"

- name: Launch AWX
  ansible.builtin.command:
    cmd: "{{ awx_launch_command }}"
    chdir: "{{ awx_source_path }}/"

AWXの最初の起動にはしばらく時間がかかるので、以下のようにリトライしながら、adminユーザーのパスワードがログに出力されるまで待ちます。

- name: Wait for AWX launched up and retrieve admin password from container log
  ansible.builtin.shell:
    "docker logs tools_awx_1 | grep 'Admin password:'"
  register: admin_password
  retries: 60
  delay: 10
  until: admin_password.rc == 0

- name: Set admin_password param
  ansible.builtin.set_fact:
    awx_admin_password_from_log: "{{ (admin_password.stdout | split(' '))[2] }}"

- name: Create password file
  ansible.builtin.copy:
    dest: "{{ awx_password_file }}"
    content: "{{ awx_admin_password_from_log }}"

adminユーザーのパスワードがログに表示されたら、それを awx_admin_password_from_log 変数で指定されたファイルに保存しておきます。

続いて、AWXのUIをビルドします。これは、docker exec コマンドをつかって、AWXのコンテナインスタンスで make clean-ui ui-devel を実行すればOKです。

- name: Build AWX UI in awx_1 container
  ansible.builtin.command: "docker exec tools_awx_1 make clean-ui ui-devel"

ここまでPlaybookがFailせずに進めば、AWXは起動したはずです。

GitLabのインストール


次はGitLabをインストール(起動)する gitlab.yml です。こちらは、アドベントカレンダーの記事に書いたように、https://hub.docker.com/r/zengxs/gitlab/tags にあるコンテナイメージを使うことにします。

まずは、必要なディレクトリを用意します(モードを0777にしちゃっている点はご愛嬌ということでw)。

- name: Create directories for GitLab
  ansible.builtin.file:
    path: "{{ item }}"
    mode: 0777
    owner: "{{ lookup('ansible.builtin.env', 'USER') }}"
    state: directory
  loop:
    - /etc/gitlab
    - /etc/gitlab/logs
    - /etc/gitlab/config
    - /etc/gitlab/data
  become: true

次に起動コマンドを作成して実行します。この際、restart_always パラメーターが true ならば、--restart always となるようにしています。

- name: Set fact - launch command
  ansible.builtin.set_fact:
    gitlab_launch_command: >-
      docker run
      --detach
      --hostname gitlab
      --publish 8143:443
      --publish 80:80
      --publish 22222:22
      --name gitlab-container
      --restart {{ 'always' if restart_always else 'no' }}
      --volume /etc/gitlab/config:/etc/gitlab:Z
      --volume /etc/gitlab/logs:/var/log/gitlab:Z
      --volume /etc/gitlab/data:/var/opt/gitlab:Z
      --net awx
      zengxs/gitlab:{{ gitlab_version }}

- name: Run gitlab-ce container
  ansible.builtin.command: "{{ gitlab_launch_command }}"

続いて、GitLabのrootユーザーパスワードを取得し、gitlab_password_file パラメーターで指定されたファイルに書き出します。

- name: Retrieve root password
  ansible.builtin.shell:
    "grep 'Password:' /etc/gitlab/config/initial_root_password"
  become: true
  register: root_password
  retries: 30
  delay: 10
  until: root_password.rc is defined and root_password.rc == 0

- name: Set gitlab_root_password param
  ansible.builtin.set_fact:
    gitlab_root_password: "{{ (root_password.stdout.split(' '))[1] }}"

- name: Create password file
  ansible.builtin.copy:
    dest: "{{ gitlab_password_file }}"
    content: "{{ gitlab_root_password }}"

後は、GitLabのUIが使えるようになるまで(実際にはサインイン画面が表示されるようになるまで)待機します。

- name: Wait for GitLab is ready
  ansible.builtin.uri:
    url: "http://localhost"
  register: top_page
  retries: 100
  delay: 10
  until: "'sign_in' in top_page.url"

GitLab Runnerもインストールする


後はGitLab Runnerのインストール(runner.yml)ですが、こちらもコンテナ自体は簡単に立ち上げられます。

まずは、ディレクトリの準備

- name: Create directories for GitLab Runner
  ansible.builtin.file:
    path: /etc/gitlab/runner
    mode: 0777
    owner: "{{ lookup('ansible.builtin.env', 'USER') }}"
    state: directory
  become: true

続いて、起動コマンドを作って実行します。

- name: Create runner run command
  ansible.builtin.set_fact:
    runner_launch_command: >-
      docker run
      --detach
      --name gitlab-runner
      --restart {{ 'always' if restart_always else 'no' }}
      --volume /etc/gitlab/runner:/etc/gitlab-runner
      --volume /var/run/docker.sock:/var/run/docker.sock 
      --net awx 
      {{ gitlab_runner_image }}

- name: Run GitLab Runner
  ansible.builtin.command: "{{ runner_launch_command }}"

これでRunner自体は起動したのですが、GitLabへの登録が必要です。

このために、まず registration token を取得するのですが、そのためのRestAPIは無いようなので、Runnerコンテナ内でgitalb railsコマンドを実行します(commandモジュールではなくcommunity.docker.docker_container_execを使うのが良いかと思いますが…)。

- name: Get registration token
  ansible.builtin.command:
    "docker exec -it gitlab-container gitlab-rails runner 
     -e production \"puts Gitlab::CurrentSettings.current_application_settings.runners_registration_token\""
  register: registration_token

gitlab railsコマンドはrunnerのコンテナインスタンス内でさらにコンテナを起動することもあって、ちょっと時間がかかりますが、仕方のないところです。

では、取得した registartion token を使ってrunnerを登録します。

- name: Register GitLab Runner
  ansible.builtin.command: "docker exec -it gitlab-runner
    gitlab-runner register --non-interactive
    --url http://gitlab/
    --registration-token {{ registration_token.stdout_lines[0] }}
    --executor docker
    --docker-image {{ gitlab_runner_container_image }}
    --name {{ gitlab_runner_name }}"

Runnerの種類としては、dockerとし、そこで使うコンテナイメージは、gitlab_runner_container_image パラメーターで指定できるようにしています。

あとは、runnerコンテナの中で動作するコンテナ(ややこしいw)からGitLabにアクセスできるよう、runnerの設定ファイルに extra_hosts の項目を追加します。

- name: Add extra_hosts to config.toml
  ansible.builtin.lineinfile:
    path: /etc/gitlab/runner/config.toml
    line: "  extra_hosts = [\"gitlab:{{ ansible_wlan0.ipv4.address }}\"]"
  become: true

これは、Runnerコンテナ内で追加するよりも、/etc/gitlab/runner/config.toml を更新する方が lineinfile モジュールが使えて楽なのでそうしました。

テストプロジェクトの追加


動作確認のために、テストプロジェクトを登録するタスク(test_project.yml)も作ってみたいと思います。

やりたい内容は次のようなものです。

  • GitLabにテストプロジェクト(リポジトリ)を作成する

  • テスト用PlaybookとCI/CDパイプラインファイル(.gitlab-ci.yml)をリポジトリに追加

  • AWXからGitLabにアクセスするための認証情報をAWXに作成

  • AWXにプロジェクトを作成

  • AWXにジョブテンプレートを作成

まず、GitLabの方からですが、プロジェクトを作成するモジュールとして community.general.gitlab_project があります。

このモジュールを実行すればプロジェクトの作成ができるのですが、このモジュールの実行のために2つやっておくことがあるので、それを先に行います。

1つは community.general コレクションのインストールで、以下のコマンドを実行します。

$ ansible-galaxy collection install community.general

もう1つがパーソナルアクセストークンの取得なのですが、これを一般的にGitLabのWeb UIで行うもので、同等のことを行うAnsibleモジュールは存在しません(たぶん)。

しかし、調べてみると、gitlab-rails コマンドをGitLabのコンテナ内で実行すれば実現できることを見つけました。

APIとレポジトリ、それからRunnerを作成できる権限で、有効期間は1年で作ることにします。

- name: Create personal access token
  ansible.builtin.command:
    "docker exec -it gitlab-container gitlab-rails runner 
      \"token = User.find_by_username('root').personal_access_tokens.create(
       scopes: ['api', 'write_repository', 'create_runner'],
       name: 'Automation token',
       expires_at: 365.days.from_now);
       token.set_token('{{ gitlab_personal_access_token }}');
       token.save!\""

リポジトリ作成の準備ができましたので、作ってみます。なお、GitLabにアクセスするためのパスワードは、gitlab_password_file で指定したファイルに保存されていますので、それを取り出して使っています。

- name: Get gitlab password
  ansible.builtin.command: "cat {{ gitlab_password_file }}"
  register: gitlab_password

- name: Add test repository to GitLab
  community.general.gitlab_project:
    api_url: http://localhost/
    api_username: 'root'
    api_password: "{{ gitlab_password.stdout }}"
    validate_certs: false
    name: testproject
    username: root
    issues_enabled: false
    merge_method: rebase_merge
    wiki_enabled: false
    snippets_enabled: true
    initialize_with_readme: true

リポジトリができれば、それをCloneして、Playbookを追加し、commit/pushします。

- name: Set user name to Git
  ansible.builtin.command:
    "git config --global user.name \"{{ ansible_user }}\""

- name: Set email address to Git
  ansible.builtin.command:
    "git config --global user.email \"{{ ansible_user }}@example.com\""

- name: Remove local repository path if exists
  ansible.builtin.file:
    path: ~/testproject
    state: absent

- name: Clone repository
  ansible.builtin.git:
    repo: "http://root:{{ gitlab_personal_access_token }}@localhost/root/testproject.git"
    dest: ~/testproject
    accept_hostkey: true

- name: Copy test playbook and ci pipeline file
  ansible.builtin.copy:
    src: files/
    dest: ~/testproject/

- name: Run git add commit and push
  ansible.builtin.command:
    cmd: "{{ item }}"
    chdir: ~/testproject
  loop:
    - "git add ."
    - "git commit -m 'committed by Ansible'"
    - "git push origin"

ちょっと泥臭いコードになってますが、ansible.scm コレクションを使うともう少しきれいになるかもしれませんね。

では、最後にAWX側の設定を行います。

まずはGitLabにアクセスするための認証情報を追加します。名前は GitLab Credential とし、credential_type には Source Control を指定します。また、パスワードには GitLab のパスワードではなく、パーソナルアクセストークンを指定しないとエラーになりますので、気を付ける必要があります。

- name: Get AWX password
  ansible.builtin.command: "cat {{ awx_password_file }}"
  register: awx_password

- name: Add gitlab credential to AWX
  awx.awx.credential:
    controller_host: 'https://localhost:8043'
    controller_username: 'admin'
    controller_password: "{{ awx_password.stdout }}"
    validate_certs: false
    name: 'GitLab Credential'
    organization: 'Default'
    state: present
    credential_type: 'Source Control'
    inputs:
      username: root
      password: "{{ gitlab_personal_access_token }}"

あとは同様にプロジェクトとジョブテンプレートを作成すれば完成です。
※プロジェクト作成時にEEのPull作業が発生し、時間がかかるため、timeoutを600秒に設定しています。

- name: Add test project to AWX
  awx.awx.project:
    controller_host: 'https://localhost:8043'
    controller_username: 'admin'
    controller_password: "{{ awx_password.stdout }}"
    validate_certs: false
    name: TestProject
    description: "This is an Arm AWX test project"
    scm_type: 'git'
    scm_url: http://gitlab/root/testproject.git
    scm_branch: 'main'
    scm_update_on_launch: true
    scm_clean: true
    scm_delete_on_update: true
    credential: 'GitLab Credential'
    organization: 'Default'
    timeout: 600

- name: Add Job Tempalte to AWX
  awx.awx.job_template:
    controller_host: 'https://localhost:8043'
    controller_username: 'admin'
    controller_password: "{{ awx_password.stdout }}"
    validate_certs: false
    name: 'Test Job Template'
    job_type: run
    organization: Default
    inventory: 'Demo Inventory'
    project: TestProject
    playbook: playbook1.yml

まとめ


今回Playbook化した際に気づいたこととして、GitLabのインストール以外はプロセッサがArmであることや、対象デバイスがラズパイであることはほとんど意識する必要がなかったことがあります。

学習用の小型コンピューターであるラズパイが、ほとんど普通のx86-64アーキテクチャのLinuxマシンと同じように設定でき、期待通り動作するのは、なかなかすごい世界だな、と思ったりしました。

あと、個人的にはGitLabのTokenに関する仕組みや操作方法が(少しかもしれませんが)学べたのは良かったし、Runnerの設定も自動化できることがわかって、得るものはいろいろありました。

出来上がったシステムはしばらく使っているのですが、問題なく使えています。ただ、ストレージに micro SD を使っているため、耐久性に関する懸念はありますね。特にGitLabのリポジトリデータは定期的に外部にバックアップするような仕掛けを作りこんでおく必要はあるかな、と思っています。

ということで、本ブログは以上となります。皆様ももしご興味持たれたら年末年始の休みにでも試してみてはいかがでしょうか?

★公式ブロガー水谷の執筆記事一覧
https://note.com/hashtag/SHIFT_%E6%B0%B4%E8%B0%B7


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

IaC支援サービスのご紹介

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

お問合せはお気軽に

SHIFTについて(コーポレートサイト)

SHIFTのサービスについて(サービスサイト)

SHIFTの導入事例

お役立ち資料はこちら

SHIFTの採用情報はこちら

PHOTO:UnsplashAltumCode