見出し画像

AnsibleでECRにDockerイメージをpushして、EKS環境にデプロイする

システムアイに出向中の松野です。今回はAnsibleを使ったコンテナのデプロイの自動化について書いてみたいと思います。

前提条件

本記事では以下の準備ができていることを前提として書いています。

  • 作業する環境にPythonとAnsible、kubectlのインストールされている

    • 本記事ではAWSのEC2にインストールしています

  • aws eks update-kubeconfig ...でconfigが作成済み

    • 後述する参考ドキュメントの手順通りに実施すればできます

  • namespaceやcontext、secretを別途作成済み

    • 動かすだけなら、作成しなくてもOK

# Ansibleのk8sモジュールを使用する際に必要なライブラリ
$  pip install pyyaml kubernetes openshift

環境について

今回はこちらの記事を参考にEKS環境を構築しました。

【初心者】Amazon EKSを使ってみる (環境構築~サンプルアプリ起動まで) - Qiita

上記からの変更点は以下となります。

  • addonのコンテナを起動するとセカンダリIPが不足するので、ノードグループのインスタンスタイプをt3.mediumに変更

    • 使用しないときはAutoScalingのノード数を0に設定すると、ワーカーノードが停止します

また、ECRへのイメージPush設定は、こちらを参考にしました。

AnsibleでAmazon Elastic Container Registry(ECR)にDockerイメージをpushする - Qii...

実装面では、こちらのソースコードも参考になりました。

ansible-for-kubernetes/cluster-aws-eks at master · geerlingguy/ansible...

構成図

以下のようなAWSの構成を考えます。

ディレクトリ構成

ディレクトリ構成

Ansibleに関しては、とりあえず以下のような必要最低限のファイルを用意しました。
ディレクトリ構成について、Red Hatの公式ページにベストプラクティスが定義されていますので、参考にしましょう。

ベストプラクティス — Ansible Documentation

.
├── local.yml            # site.yml から呼び出されて実行されるファイル。
├── site.yml             # 本YAMLファイルから、[任意の処理名].ymlを呼び出す。
├── Dockerfile           # pushするDockerイメージ。
├── Jenkinsfile          # イメージに含める、適当なJenkinsfileです。
├── inventory            # ホストとそれに紐づく変数を定義するファイル。
│     └── hosts
└── roles                # Playbookのタスクetc...を格納。詳細は公式ドキュメント参照。
     ├── ...
     └── k8              # role毎にディレクトリを用意すると良さそう。
         └── tasks
             ├── deployment.yml   # EKSへデプロイするマニフェスト。
             └── main.yml    # Dockerイメージのビルドやpush等の実処理を記載するファイル。

実装

では、AnsibleのPlaybookやRoleを実装していきましょう。

site.yml

実行起点となるPlaybookを"site.yml"としますが、これは別のyamlファイルをインポートするだけのものにします。

---
- import_playbook: local.yml

local.yml

このファイル内で実行対象のroleをrolesの配列に含めます。

なお、ansible.kubernetes-modules は、k8sデプロイモジュールを利用する際に必要になります。

---
- name: Ansible Test
  hosts: local
  roles:
    - ansible.kubernetes-modules
    - k8s

inventory/hosts

ローカル環境の定義のみ記載しています。
ベストプラクティスによると、変数定義はgroup_varsやhost_varsディレクトリにまとめて配置するようです。今回はクレデンシャル情報をそのまま記述していますが、実際にプロジェクトで使用する場合は、そのプロジェクトの方針に従うことになります。

[local]
localhost ansible_connection=local

[local:vars]
region=ap-northeast-1
key_id=xxxxxxxx
access_key=xxxxxxxx
image_name=jenkins-test/jenkins-test
image_version=v1.0.0

roles/k8s/tasks/main.yml

AWSコマンドを使用する為、configなどの配置も併せて行っています。
埋め込み文字は Jinja2 構文 で変数が展開されます。

なお、pipモジュールのインストールは配列形式で一度に複数指定できるようですので、まとめて記載した方が良いかもしれません。

---

- name: "push Docker image to ECR & deploy to EKS"
  debug: "push Docker image to ECR & deploy to EKS"

- name: make aws directory
  file:
    dest: ~/.aws
    state: directory
    mode: u=rwx,g=o=

- name: copy aws config
  template:
    src: aws/config
    dest: ~/.aws/config
    mode: u=rw,g=o=

- name: copy aws credentitals
  template:
    src: aws/credentitals
    dest: ~/.aws/credentitals
    mode: u=rw,g=o=

- name: install docker-py for build Docker
  pip:
    name: docker-py
    state: present

- name: build Docker image
  docker_image:
    build:
      path: ./
    name: "{{ image_name }}"
    tag: "{{ image_version }}"

- name: docker login
  shell: "aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin <ユーザーID>.dkr.ecr.ap-northeast-1.amazonaws.com"
  args:
    executable: /bin/bash

- name: install boto3
  pip:
    name: boto3
    state: present

- name: create repository
  ecs_ecr:
    name: "{{ image_name }}"
    aws_access_key: "{{ key_id }}"
    aws_secret_key: "{{ access_key }}"
    region: "{{ region }}"
  register: ecr_repo

- name: add tag
  docker_image:
    name: "{{ image_name }}:{{ image_version }}"
    repository: "{{ ecr_repo.repository.repositoryUri }}"
    tag: "{{ image_version }}"

- name: push image to ecr
  docker_image:
    name: "{{ ecr_repo.repository.repositoryUri }}:{{ image_version }}"
    push: yes

- name: Create deployment to EKS
  vars:
    imagename: "{{ ecr_repo.repository.repositoryUri }}:{{ image_version }}"
    replica: 1
    namespace: ansible-test
  k8s:
    definition: '{{ item }}'
    kubeconfig: ~/.kube/config
    state: present
  loop:
    - "{{ lookup('template', 'deployment.yml') | from_yaml_all | list }}"

roles/k8s/tasks/deployment.yml

EKSへデプロイするマニフェストファイルです。
playbookで定義したvarsの各値がマニフェストの埋め込み文字に反映されますので、環境差分にも対応できそうです。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-jenkins-app
  namespace: {{ namespace }}
spec:
  replicas: {{ replica }}
  selector:
    matchLabels:
      app: test-jenkins-app
  minReadySeconds: 10
  template:
    metadata:
      labels:
        app: test-jenkins-app
    spec:
      volumes:
      - name: git-secret-volume
         secret:
           secretName: git-secret

    containers:
    - image: {{ imagename }}
       name: jenkins-test-container
       restartPolicy: Always
       ports:
       - containerPort: 8080
       resources:
         requests:
           cpu: 200m
           memory: 20Mi
         limits:
           cpu: 512m
           memory: 500Mi
       volumeMounts:
       - name: git-secret-volume
          mountPath: /var/jenkins_home/.ssh
          readOnly: true

---

apiVersion: v1
kind: Service
metadata:
  name: test-jenkins-service
  namespace: {{ namespace }}
  labels:
    app: test-jenkins-service
spec:
  type: LoadBakancer
  ports:
  - port: 80
     protocol: TCP
     targetPort: 8080
  selector:
    app: test-jenkins-app

Dockerfile

さて、対象となるコンテナですが、とりあえずJenkinsのコンテナを動かすことにしましょう。Dockerfileは以下のような内容にしました。

FROM jenkins/jenkins:latest

RUN mkdir -p /var/jenkins_home/.ssh && ¥
         chown -R jenkins:jenkins /var/jenkins_home

COPY --chown=jenkins:jenkins ./Jenkinsfile ./

Playbook実行

では、準備が整いましたので、Playbookを実行して各ジョブが正常終了することを確認します。

$ ansible-playbook site.yml -i inventory/hosts

PLAY [Ansible Test] ********************************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************************
[WARNING]: Platform linux on host localhost is using the discovered Python interpreter at /usr/bin/python, but future installation of
another Python interpreter could change this. See https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html
for more information.
ok: [localhost]

TASK [ansible.kubernetes-modules : Install latest openshift client] ********************************************************************
skipping: [localhost]

TASK [k8s : push docker image to ECR & deploy to EKS] ******************************************************************************
ok: [localhost] => {
    "msg": "push docker image to AWS ECR & deploy to EKS"
}

TASK [k8s : make aws directory] ********************************************************************************************************
ok: [localhost]

TASK [k8s : copy aws config] ***********************************************************************************************************
ok: [localhost]

TASK [k8s : copy aws credentials] ******************************************************************************************************
ok: [localhost]

TASK [k8s : install docker-py for build docker] ****************************************************************************************
ok: [localhost]

TASK [k8s : build docker image] ********************************************************************************************************
[WARNING]: The value of the "source" option was determined to be "build". Please set the "source" option explicitly. Autodetection will
be removed in Ansible 2.12.
ok: [localhost]

TASK [k8s : docker login] **************************************************************************************************************
changed: [localhost]

TASK [k8s : install boto3] *************************************************************************************************************
ok: [localhost]

TASK [k8s : create repository] *********************************************************************************************************
ok: [localhost]

TASK [k8s : add tag] *******************************************************************************************************************
[WARNING]: The value of the "source" option was determined to be "pull". Please set the "source" option explicitly. Autodetection will
be removed in Ansible 2.12.
ok: [localhost]

TASK [k8s : push image to ecr] *********************************************************************************************************
ok: [localhost]

TASK [k8s : Create a Deployment to EKS] ************************************************************************************************
ok: [localhost] => (item=[{u'kind': u'Deployment', u'spec': {u'selector': {u'matchLabels': {u'app': u'test-jenkins-app'}}, 
....
u'metadata': {u'labels': {u'app': u'test-jenkins-service'}, u'namespace': u'ansible-test', u'name': u'test-jenkins-service'}}])

PLAY RECAP *****************************************************************************************************************************
localhost                  : ok=13   changed=1    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

無事成功したようです。

起動確認

参考サイトや公式ドキュメントの手順通りに設定していれば、IAMの設定はできていると思います。
以下のコマンドでJenkinsのPodが動いていて、サービスが作成されていることを確認しました。

$ alias k=kubectl

$ k get pod --namespace ansible-test
NAME                                READY   STATUS    RESTARTS   AGE
test-jenkins-app-7c7ffcfb46-fq5q6   1/1     Running   0          46m

# コンテナにログインして、jenkinsのinitialAdminPasswordを確認
$ k exec -it test-jenkins-app-7c7ffcfb46-fq5q6 -n ansible-test -- /bin/bash

jenkins@test-jenkins-app-7c7ffcfb46-fq5q6:/$ cat /var/jenkins_home/secrets/initialAdminPassword
xxxxxxxxxxxxxxxxxxxxxxxxxxx

jenkins@test-jenkins-app-7c7ffcfb46-fq5q6:/$ exit
exit

# アクセス先を確認
$ k get service --namespace ansible-test
NAME                   TYPE           CLUSTER-IP       EXTERNAL-IP                                                                    PORT(S)        AGE
test-jenkins-service   LoadBalancer   10.100.138.217   ac7d2f38812bc41d7bf81a39a4a7c467-1945438877.ap-northeast-1.elb.amazonaws.com   80:32108/TCP   3d17h

これで、http://<EXTERNAL-IP>にアクセスすると、jenkinsの初期画面が表示されるまずです。

やや余談ですが、HTTPS接続にしたい場合、ACMで証明書を作成すると良いでしょう。
――――――――――――――――――――――――――――――――――

執筆者プロフィール:松野 聖弘
前職はSESの会社でWebシステムの設計・構築に携わっており、Java、Python、Node.js等を用いたフルスタック開発やk8s環境へのリリース、CICDパイプライン構築を担当。
現在はその経験を活かして、k8s環境やCICDパイプラインの導入支援、コンサル業務に従事している。

【ご案内】
ITシステム開発やITインフラ運用の効率化、高速化、品質向上、その他、情シス部門の働き方改革など、IT自動化導入がもたらすメリットは様々ございます。

IT業務の自動化にご興味・ご関心ございましたら、まずは一度、IT自動化の専門家リアルグローブ・オートメーティッド(RGA)にご相談ください!

お問合せは以下の窓口までお願いいたします。

【お問い合わせ窓口】
窓口:rga@systemi.co.jp
URL:https://rg-automated.jp