Day-to-day the memorandum

やったことのメモ書きです。

GCPでElasticsearchのGCS Repository Pluginがインストールされたカスタムイメージを作ってみる

Elasticsearch Serviceがある今、自前でインスタンスを立ててElasticsearchの環境を作るという機会はなかなかないように思えますが、やったのでメモです。

ElasticsearchのSnapshotをGCSに上げるための設定をGCPのサービス群とPacker,Ansibleなどを使って実現する方法を書いていきます。

はじめに

まずElasticsearchのGCS Repository Pluginの設定の仕方について確認します。

  1. Pluginをインストールする
  2. GCSのBucketを作成する
  3. Service Accountを作成し、GCSに読み書きできる権限をつける
  4. Service AccountをElasticsearch keystoreに登録する

Google Cloud Storage Repository Plugin | Elasticsearch Plugins and Integrations [6.6] | Elastic
Getting started | Elasticsearch Plugins and Integrations [6.6] | Elastic
Client Settings | Elasticsearch Plugins and Integrations [6.6] | Elastic
Repository Settings | Elasticsearch Plugins and Integrations [6.6] | Elastic

PluginのインストールとService AccountのJSONキーをElasticsearch Keystoreに登録するところをAnsibleで行います。
そして、その構成のカスタムイメージをPackerで作ってみようと思います。

ディレクトリ構成

elasticsearch
├─group_vars
│  └─all
│      ├─vars.yml
│      └─vault.yml
├─roles
│  ├─elastic.elasticsearch //←省略
│  └─elasticsearch_others
│      ├─defaults
│      │  └─main.yml
│      ├─tasks
│      │  └─main.yml
│      └─templates
│          └─credentials_file.j2
├─cloudbuild.yaml
├─packer.json
├─requirements.yml
├─site.yml
└─vault-password-file.enc

Ansibleの作成

Elasticsearchの基本的な設定

公式がAnsibleのRoleを用意してくれているのでこれを使います。

GitHub - elastic/ansible-elasticsearch: Ansible playbook for Elasticsearch

適当にAnsibleを書いていきます。

  • requirements.yml
---
- src: elastic.elasticsearch
  version: 6.6.0
  • site.yml
---
- hosts: all
  become: yes
  connection: local
  roles:
    - elastic.elasticsearch  #←ElasticのRole
    - elasticsearch_others #←GCS Repository Plugin関連
  • group_vars/all/var.yml
---
es_instance_name: "node"
es_config:
  cluster.name: "sample"
  node.name: "${HOSTNAME}"
  node.data: true
  node.master: true
  network.host: "0.0.0.0"
  discovery.zen.hosts_provider: "gce"
  cloud.gce.zone: ["asia-northeast1-a", "asia-northeast1-b", "asia-northeast1-c"]
  bootstrap.memory_lock: true
es_plugins:
  - plugin: discovery-gce
  - plugin: repository-gcs

設定は割と適当です。
あと、ついでにGCE Discovery Pluginもインストールするようにします。
そうすることで、MasterノードのIPアドレスを指定することなく、クラスタリングが組めるようになります。
作成するGCEのインスタンスには「Cloud API アクセス スコープ」の「Compute Engine」を読み書き権限を付ける必要があります。

GCE Discovery Plugin | Elasticsearch Plugins and Integrations [6.6] | Elastic
Setting up GCE Discovery | Elasticsearch Plugins and Integrations [6.6] | Elastic

Elasticsearch Keystore周辺

Ansible Valut

KeystoreにService AccountのJSONキーを登録していくのですが、JSONキーをGitに含めるわけにはいかないので、ここでAnsible Vaultを使って暗号化します。

以下のようにAnsibleのVariableファイルを用意します。

  • group_vars/all/vault.yml
---
service_account_json: |
  {
    "type": "service_account",
    "project_id": "[PROJECT-ID]",
    "private_key_id": "[KEY-ID]",
    "private_key": "-----BEGIN PRIVATE KEY-----\n[PRIVATE-KEY]\n-----END PRIVATE KEY-----\n",
    #省略
  }

以下コマンドで暗号化します。この時に復号用のパスワードを設定します。

$ cd group_vars/all
$ ansible-vault encrypt vault.yml
Vault password: 
Confirm Vault password: 
Encryption successful
Ansible Task

上で定義した変数を使ってService AccountのJSONキーを登録していきます。

  • roles/elasticsearch_others/templates/credentials_file.j2
{{ service_account_json }}
  • roles/elasticsearch_others/tasks/main.yml
---
- name: create file
  template:
    src: credentials_file.j2
    dest: /tmp/credentials_file

- name: check service account set keystore
  shell: "{{ es_home }}/bin/elasticsearch-keystore list | grep gcs.client.default.credentials_file"
  environment:
    ES_PATH_CONF: "{{ conf_dir }}"
  register: service_account_keystore
  ignore_errors: yes

- name: add service account
  shell: "{{ es_home }}/bin/elasticsearch-keystore add-file gcs.client.default.credentials_file /tmp/credentials_file"
  environment:
    ES_PATH_CONF: "{{ conf_dir }}"
  when: service_account_keystore | failed

- name: delete file
  file:
    path: /tmp/credentials_file
    state: absent

elasticsearch-keystore add-fileでしかJSONキーをうまく登録できなかったので、いったんJSONキーをファイルに書き出して、登録した後に消しています。

Cloud KMS

Ansible VaultにてJSONキーは暗号化しましたが、Ansible Vaultの複合するためのパスワードがあり、このパスワードもGitに含めるわけにはいきません。
そこでAnsible Vaultの復号用パスワードをCloud KMSを使って管理します。

以下、クイックスタートどおりに行っていきます。
クイックスタート  |  Cloud KMS ドキュメント  |  Google Cloud

$ gcloud kms keyrings create ansible-keyring --location global
$ gcloud kms keys create ansible-key --location global  --keyring ansible-keyring --purpose encryption
$ echo -n "Some text to be encrypted" > vault-password-file #先ほど設定したAnsible Vaultのパスワード
$ gcloud kms encrypt --location global --keyring ansible-keyring --key ansible-key --plaintext-file vault-password-file --ciphertext-file vault-password-file.enc

これでAnsible Vaultの復号用パスワードの暗号化もできました。

Packerの作成

packer.jsonを作成していきます。

{
  "builders": [
    {
      "type": "googlecompute",
      "project_id": "{{user `project_id`}}",
      "source_image_family": "debian-9",
      "zone": "asia-northeast1-a",
      "disk_size": 20,
      "image_name" : "elasticsearch-{{timestamp}}",
      "image_description": "elasticsearch",
      "ssh_username": "packer"
    }
  ],
  "provisioners": [
    {
      "type": "shell",
      "inline": [
        "sudo apt update",
        "sudo apt install -y python python-pip",
        "sudo pip install ansible==2.7.0",
        "sudo pip install jmespath==0.9.3"
      ]
    },
    {
      "type": "ansible-local",
      "playbook_dir": ".",
      "playbook_file": "site.yml",
      "galaxy_file": "requirements.yml",
      "extra_arguments": [
        "--vault-password-file","vault-password-file",
        "-vvvv"
      ]
    },
    {
      "type": "shell",
      "inline": [
        "rm -rf /tmp/packer-provisioner-ansible-local"
      ]
    }
  ]
}

provisionerstypeansibleではなくansible-localを使ったのはgalaxyが使えなかったからです。
"playbook_dir": "."でカレントディレクトリを対象のインスタンスに持って行ってくれます。
--vault-password-fileに復号後のファイルを指定します。

Cloud Build

次にPackerをCloud Buildで実行してカスタムイメージを作ってみます。

---
steps:
- name: 'gcr.io/cloud-builders/gcloud'
  args:
  - kms
  - decrypt
  - --ciphertext-file=vault-password-file.enc
  - --plaintext-file=vault-password-file
  - --location=global
  - --keyring=ansible-keyring
  - --key=ansible-key

- name: 'hashicorp/packer:1.3.3'
  args:
  - build
  - -var
  - project_id=$PROJECT_ID
  - packer.json

暗号化されたファイルを復号して、その後にPackerを実行しています。

あとはCloud Buildを実行すればカスタムイメージが作成されると思います。

$ gcloud builds submit --config cloudbuild.yaml ./

おわりに

Cloud Build, Cloud KMS, Ansible, Packerを使ってGitにセンシティブな情報を含めずにElasticsearchのGCS Repository Pluginがインストールされたカスタムイメージを作ってみました。
バケツリレー感が否めないですね。。。
その他、運用面とか考えるとElasticsearch Service(Elastic Cloud)使うのが楽そうでいいなーと思いました。