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で証明書を作成すると良いでしょう。
――――――――――――――――――――――――――――――――――
【ご案内】
ITシステム開発やITインフラ運用の効率化、高速化、品質向上、その他、情シス部門の働き方改革など、IT自動化導入がもたらすメリットは様々ございます。
IT業務の自動化にご興味・ご関心ございましたら、まずは一度、IT自動化の専門家リアルグローブ・オートメーティッド(RGA)にご相談ください!
お問合せは以下の窓口までお願いいたします。
【お問い合わせ窓口】
窓口:rga@systemi.co.jp
URL:https://rg-automated.jp