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

目的

  • EC2から、AWS EKSにansibleでデプロイしてみる
    • AWS ECRにDockerイメージをプッシュする
  • とりあえず動かしてみて、各ツールの使い方を学習する

前提条件

  • 作業する環境にPythonAnsiblekubectlのインストールが必要
  • aws eks update-kubeconfig ...でconfigが作成済み
# Ansibleのk8sモジュールを使用する際に必要なライブラリ
$  pip install pyyaml kubernetes openshift

参考

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

qiita.com

変更点は以下となります。

  • 社用アカウントのVPC数が上限に達していたので、CloudFormationによるデプロイはせず、既存のVPCを利用
  • addonのコンテナを起動するとセカンダリIPが不足するので、ノードグループのインスタンスタイプをt3.mediumに変更
    • 使用しないときはAutoScalingのノード数を0に設定すると、ノードが停止します

構成図

ディレクトリ構成

 とりあえず、必要最低限のファイルを用意しました。
ディレクトリ構成について、公式がベストプラクティスを定義していますので、そちらが参考になります。

docs.ansible.com

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

実装

site.yml

 実行ファイルをインポート。

---
- import_playbook: local.yml
local.yml

 実行対象のroleをrolesの配列に含める。
ansible.kubernetes-modulesk8sデプロイモジュールを利用する際に必要になります。
先にインストール済みの環境であれば、不要かも?

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

 ローカル環境の定義のみ記載しています。
ベストプラクティスによると、変数定義はgroup_varshost_varsディレクトリに纏めて配置するようです。
クレデンシャル管理はPJの方針に従ってください。
 また、最初はinventoriesという名前のディレクトリにしていたのですが、--syntax-chek実行時に警告?が出ていたので、とりあえずinventoryとしています。

[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 構文 で変数が展開されます。

---

- name: "Push Docker image to AWS ECR"
  debug: "Push Docker image to AWS ECR"

- 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へデプロイするマニフェストファイル。
ジョブで定義した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を動かしてみます。

FROM jenkins/jenkins:latest

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

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

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'minReadySeconds': 10,
....
u'spec': {u'type': u'LoadBalancer', u'ports': [{u'targetPort': 8080, u'protocol': u'TCP', u'port': 80}], u'selector': {u'app': u'test-jenkins-app'}}, u'apiVersion': u'v1', 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の設定はできていると思います。

$ 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で証明書を作成すると良いでしょう。