- 投稿日:2019-10-24T22:54:48+09:00
ECRでコンテナ脆弱性診断が実装されていたので試してみた
ECRでスキャン?
今日の業務中にふとECR画面見たら、「スキャン」の表示を見つける。
コンテナの脆弱性診断が実装されてる?
ん?こんなのあったっけ。見落としていただけかな。ということで触ってみた。触ってみた
リポジトリ作成
「プッシュ時にスキャン」画面メニューがある。とりあえず、リポジトリ作成してみる。
なるほど、docker push
でコンテナ登録する時にスキャンが走るようだ。有効にしてみる。コンテナをECRに登録
試しにcentosのコンテナをpushして、スキャンをしてみる。
docker pull centos:latest docker tag centos:latest [AWSアカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/centos:1.0 $(aws ecr get-login --no-include-email --region ap-northeast-1) docker push [AWSアカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/centos:1.0・
centos:latest
のコンテナをローカルに落とす。
・ECR用のタグを設定。
・ECRログイン。
・ECRのcentos
リポジトリにpushする。スキャン完了。そして脆弱性確認へ・・・
スキャンは完了!脆弱性があるようなので、詳細を見てみる。
概要で脆弱性の件数とパッケージ毎の脆弱性説明が表示される。
例では重要度がCritical
はないが、High
とMedium
がそれぞれ指摘された。
名前がリンクになっているのでクリックすると・・・脆弱性の対応をしてみる。
パッケージのバージョンアップをすることで、脆弱性指摘が減るか確認してみる。
FROM centos:latest LABEL maintainer "portfield" RUN yum -y update && yum clean allOSアップデートする
Dockerfile
を書く。シンプル。
centos
ディレクトリにファイルを配置。docker build -t [AWSアカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/centos:1.1 centos docker push [AWSアカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/centos:1.1・ECR用でバージョン上げてDockerイメージ作成。
・ECRのcentos
リポジトリにpushする。再びスキャン完了。脆弱性確認へ・・・
上がOSアップデート前、下がOSアップデート後のDockerイメージ。
脆弱性件数が減っていることから、OSアップデートにより、脆弱性が減ったことが分かる。
詳細も見てみよう。
OSアップデートだけでは対処出来なかった脆弱性が表示される。
名前のリンクと説明を頼りに対応しよう。今回は対応しない。。スキャンを有効にしていなくてもOK。
まとめ
・以上のように、リポジトリの設定を有効にするだけで、簡単に使用することが出来た。
CI/CDの一環でソース更新時にDockerイメージを自動作成し、ECRにpushしているケースも多いと思うので、便利に使用出来そう。
・というか、元々あったのだろうか???ドキュメント探しても見つからない。。
裏で何をベースにスキャンしているとか、情報があったら欲しいです。。
- 投稿日:2019-10-24T22:54:48+09:00
ECRでコンテナ脆弱性診断が標準実装されていたので試してみた
※2019/10/25(金)追記:
本機能は使用出来なくなっているようです。ECRでスキャン?
今日の業務中にふとECR画面見たら、「スキャン」の表示を見つける。
コンテナの脆弱性診断が実装されてる?
ん?こんなのあったっけ。見落としていただけかな。ということで触ってみた。リポジトリ作成
「プッシュ時にスキャン」画面メニューがある。とりあえず、リポジトリ作成してみる。
なるほど、docker push
でコンテナ登録する時にスキャンが走るようだ。有効にしてみる。コンテナをECRに登録
試しにcentosのコンテナをpushして、スキャンをしてみる。
docker pull centos:latest docker tag centos:latest [AWSアカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/centos:1.0 $(aws ecr get-login --no-include-email --region ap-northeast-1) docker push [AWSアカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/centos:1.0・
centos:latest
のコンテナをローカルに落とす。
・ECR用のタグを設定。
・ECRログイン。
・ECRのcentos
リポジトリにpushする。スキャン完了。そして脆弱性確認へ・・・
スキャンは完了!脆弱性があるようなので、詳細を見てみる。
概要で脆弱性の件数とパッケージ毎の脆弱性説明が表示される。
例では重要度がCritical
はないが、High
とMedium
がそれぞれ指摘された。
名前がリンクになっているのでクリックすると・・・脆弱性の対応をしてみる。
パッケージのバージョンアップをすることで、脆弱性指摘が減るか確認してみる。
FROM centos:latest LABEL maintainer "portfield" RUN yum -y update && yum clean allOSアップデートする
Dockerfile
を書く。シンプル。
centos
ディレクトリにファイルを配置。docker build -t [AWSアカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/centos:1.1 centos docker push [AWSアカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/centos:1.1・ECR用でバージョン上げてDockerイメージ作成。
・ECRのcentos
リポジトリにpushする。再びスキャン完了。脆弱性確認へ・・・
上がOSアップデート前、下がOSアップデート後のDockerイメージ。
脆弱性件数が減っていることから、OSアップデートにより、脆弱性が減ったことが分かる。
詳細も見てみよう。
OSアップデートだけでは対処出来なかった脆弱性が表示される。
名前のリンクと説明を頼りに対応しよう。今回は対応しない。。スキャンを有効にしていなくてもOK。
まとめ
・以上のように、リポジトリの設定を有効にするだけで、簡単に使用することが出来た。
CI/CDの一環でソース更新時にDockerイメージを自動作成し、ECRにpushしているケースも多いと思うので、便利に使用出来そう。
・というか、元々あったのだろうか???ドキュメント探しても見つからない。。
裏で何をベースにスキャンしているとか、情報があったら欲しいです。。
- 投稿日:2019-10-24T17:16:20+09:00
コンテナ基礎 Dockerとkubernetes
はじめに
コンテナの基礎勉強会の内容メモです。
コンテナ
OSSの仮想化技術。
docker
アプリケーションの動作環境をコンテナにデプロイするための単位。
VMと比べて軽量で高速。コンテナへデプロイの流れ
1.コンテナを定義(DockerFile)
2.コンテナをDockerFile通りに作成、保管。
3.Dockerのimageから起動
VMとの違い
OSの上にHypervisorが存在せずカーネルが存在する。コンテナにはOS自体はなく一つのサーバの上でライブラリのようにLinuxやWindowsが動く。
メリット
・アプリケージョンの環境差異を減らせる。
・テスト環境では動くが本番環境で動かないという事態を防げる。
・環境構築やセットアップの手間を減らせる。kubernetes
1つのホストではなく複数のホストで動かしたい場合
・docker-composeを使う方法
単一のホストで複数のコンテナを組み合わせて実行する仕組み
のdocker-composeだと自動復旧やアップデートができない。・kubernetesを使う方法
kubernetesは複数のホスト上でコンテナを適切に動かし自動復旧できる。開発側のメリット
OS以下の層に過度な注意を向けることなく開発に専念できる。
ただしコンテナの中のソースコードやミドルウェアは意識しなくてはならない。コンテナを用いた設計
・ステートレス
コンテナが停止した場合も想定してにコンテナの外にセッションやファイルを管理する。
・1つのコンテナに1つの役割
tomcat,apache,mysqlなどそれぞれを別のコンテナに管理しなくてはならない。例え
コンテナはレトルトや冷凍食品のようにいつでもどこでも同じ味で簡単に作れる。
自分で環境構築する場合は1からチャーハンを作るようなもの。
- 投稿日:2019-10-24T15:31:28+09:00
今更だけどDockerコンテナの脆弱性をRancher経由で利用されroot権を渡してしまった話
「脆弱性」というほど大げさなものではなく、Dockerが普及した頃から注意喚起されていた内容ですが、仲間内の環境ということもあり油断していました。今回は戒めの意味も込めて記事を書かせていただきます。
使用していた環境
- VPS(2コア/8GB Ubuntu 18.04) *2台
- VPS1台は Docker + Kubernetes を管理するためRancherマスターとして使用
- 残りの1台はRancher管理下のホストとしてKubernetesを構成…デプロイ用
- 権限の状況(どちらもログインできるのは私のみ)
- VPS/Rancherマスター…sudo, docker グループに所属するユーザー1個。公開鍵でのみssh可能
- VPS/ホスト…sudo, docker グループに所属するユーザー1個。公開鍵でのみssh可能
- 環境を使用する人
- 私
- 趣味で一緒にコードを書いている友達
- 環境構築が面倒なのでRancherの構築などは私に投げている
- こいつが今回の犯人
脆弱性への攻撃内容
dockerコンテナをrunする際に「-v」オプションでVPSマシンの "/" と、コンテナの指定ディレクトリを関連付け、 "chroot"を実行。
コンテナ上からVPSマシンに対してroot権限であらゆる書き換えを実行。(sshd_config 書き換えなど)情報はこちらを参考にさせていただきました。
- Don't expose the Docker socket (not even to a container)
- コンテナ・セキュリティ入門 と Kubernetes(qiita記事)上記をdocker コマンド で実行した場合の手順 ※説明のために記載します。実際に実行はしないでください。
docker run -it -v /:/root debian /bin/bash #ローカルマシンのロートディレクトリ"/"をdebianコンテナの"/root"ディレクトリと同期させた状態で実行 root@{コンテナID}:/# cat /etc/hostname {コンテナID} #コンテナ上でbashを実行していますので、当然コンテナの名前が返ってきます。 root@{コンテナID}:/# chroot /root :# /bin/bash #ローカルマシンの"/"と関連付けている"/root"に対してchrootを実行して"/"の操作が可能に。試しにbashを起動してみる.. root@{コンテナID}:/# cat /etc/hostname {ローカルマシンのホスト名} #同じコマンドを実行しましたが、今度はローカルマシンの名前が出てきました。あとはやりたい放題です。 root@{コンテナID}:/# vi /etc/ssh/sshd_config root@{コンテナID}:/# ps aux root@{コンテナID}:/# lsof -i -P #などなど..Rancher で実行された内容
- adminユーザー(私)が新規ユーザー(犯人)を作成。
- 作成したユーザーに最低限の権利を付与。
- 新規ユーザーがログイン。
- プロジェクトの作成権限があるクラスターでコンテナを作成。
- 以降は上記のdockerコマンド実行内容と同様。
以下Rancherの操作画面を使って説明いたします。
1.adminユーザー(私)が新規ユーザー(犯人)を作成。
adminでログインし、「グローバル」タブの「ユーザー」を選択しユーザーを追加する。
ユーザーの内容は以下で作りました。
ユーザー名: waruihito
パスワード: waruihito
表示名: waruihito
グローバル権限: 一般ユーザー
2.作成したユーザーに最低限の権利を付与。
クラスターのメンバーにも新規ユーザーを追加。(今回のクラスタ名は「sandbox」)
今回は最低限の権限として「Create Project」のみを付与します。
3.新規ユーザーでログイン。
作成したユーザーでログインし直すと、権限が振られているクラスターのみが表示されます。
「Create Project」権限を持っているので、新規プロジェクト「warudakumi」を作成します。
4.クラスターでコンテナを作成。
先程の「warudakumi」プロジェクトで、「ワークロード」から乗っ取り用のコンテナをデプロイしていきます。
5.以降は上記のdockerコマンド実行内容と同様。
作成したコンテナ(ポッド)でシェルを実行します。(Rancherではプルダウンから選択して起動できます。)
学んだこと
Docker単体で運用するときには結構気を使っている部分ですし、公式のリファレンスにも「ここに気をつけて!」みたいな文言は書いてあります。
しかしk8sやRancherなどで運用する際には「それは知ってて当然だよね?」という暗黙のルールのもと構築をしてしまうことで、このような初歩的な脆弱性を疲れることになりました。(いや口で言ってくれたら分かるよ?わざわざsshd_config書き換える必要ある?印象には残ったけどね!)基本大事!効率的にするためであって、横着してもいいわけではないということだと思います。
- 投稿日:2019-10-24T12:47:36+09:00
Dockerのホスト側でデータベース復元
環境
Docker
準備
直接パスワード入れるのはアレなので、
設定ファイル作っておく。また、docker-compose.ymlでボリュームとか設定する。
このデータ共有されてるホスト側のパスを後ほど指定する。シェル
doHukugen.shdocker exec -i {コンテナID} mysql --defaults-file=db.conf {データベース名} < ../migration/datastore/back19_10_18.dumpオプションについて
オプションについては下記に詳しく書いてある。
https://hodalog.com/how-to-resolve-the-error-that-the-input-device-is-not-a-tty/dockerコマンドの-tオプションは「TTYを割り当ててくれる」オプション。
このオプションを外すことでcronジョブが正常に実行できるようになります。
なので、tをつけると、TTYがないみたいなエラーが出るからつけない。ついでに-iは「標準入力を開き続ける」オプションで、これもCLIでコンテナを操作するために必要なオプション。
これはもちろん必要なので今回はこのオプションだけ。結果
このシェルスクリプトを実行してみると、指定したダンプファイルの状態にデータが戻った
- 投稿日:2019-10-24T02:46:29+09:00
Kubernetes in docker (kind) on Mac で type:LoadBalancer もマルチノードもお手軽に遊ぶ
前置き
なぜ Docker Desktop の Kubernetes でないのか?
- 重い。(感覚値)
- 環境をきれいに保てない。(自身のスキル不足かも)
- マルチノードで遊びたい。
- マルチクラスタで遊びたい。
- あわよくばテスト環境として使いたい。
動かしている環境
- macOS 10.14.5
- Docker Desktop 2.1.0.3
- go version go1.11.1 darwin/amd64
kind とは?
kind is a tool for running local Kubernetes clusters using Docker container "nodes".
- Docker の 1 コンテナを 1 ノードとして Kubernetes Cluster を実現するもの。
- どこまで使えるか、kind 特有の問題に悩ませられないかは、今後、使用していって確かめる。
- 使い込むのはこれからなので、今は書けない。
- kind に似たようなやつにこんなのもある。
- Kubeadm-dind
- 廃止されてた。
- k3d
- 使い込んだわけではないけれど kind と比べるとイマイチ感。(主観)
- そもそもコンセプトが違うのか。
kind をインストールしただけでは Service で LoadBalancer が使えない
- MetalLB (Google 製) を入れて
type: LoadBalancer
もいけるようにする。kind のインストール
kind version v0.5.1
と出たら OK 。(2019-10-20 現在)curl -Lo ./kind https://github.com/kubernetes-sigs/kind/releases/download/v0.5.1/kind-$(uname)-amd64 chmod +x ./kind mv ./kind /usr/local/bin/ kind versionKubernetes cluster を作成
- Multi-node clusters を参照。
cat <<EOF > config.yaml kind: Cluster apiVersion: kind.sigs.k8s.io/v1alpha3 nodes: - role: control-plane - role: worker - role: worker EOF
- 下記コマンドで、クラスター作成。しばらく時間がかかる。
kind create cluster --name kind1 --config config.yaml
- クラスターが作成されると下記が出力されるので、言われるがままに実行する。
- 下記実行前に
echo $KOBECONFIG
の値をコピっとく。export KUBECONFIG="$(kind get kubeconfig-path --name="kind1")" kubectl cluster-info
- ノードを取得してみる。
$ kubectl get node NAME STATUS ROLES AGE VERSION kind1-control-plane Ready master 3m57s v1.15.3 kind1-worker Ready <none> 3m17s v1.15.3 kind1-worker2 Ready <none> 3m17s v1.15.3
- Docker のコンテナを表示してみる。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES d01aa2c77cc2 kindest/node:v1.15.3 "/usr/local/bin/entr…" 9 minutes ago Up 9 minutes kind1-worker2 a9395bd71807 kindest/node:v1.15.3 "/usr/local/bin/entr…" 9 minutes ago Up 9 minutes kind1-worker f91f98a8f6b4 kindest/node:v1.15.3 "/usr/local/bin/entr…" 9 minutes ago Up 9 minutes 63002/tcp, 127.0.0.1:63002->6443/tcp kind1-control-planeクラスターの後始末の方法を確認しておく。
KUBECONFIG
に元の値をセットする。kind get clusters kind delete cluster --name kind1 export KUBECONFIG=~/.kube/configMetalLB のデプロイ
kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.8.1/manifests/metallb.yaml
kubectl apply -f - -o yaml << 'EOF' apiVersion: v1 kind: ConfigMap metadata: namespace: metallb-system name: config data: config: | address-pools: - name: default protocol: layer2 addresses: - 192.168.1.2-192.168.1.254 EOFKubernetes のチュートリアルをやってみる
kubectl apply -f - -o yaml << 'EOF' apiVersion: apps/v1 kind: Deployment metadata: labels: app.kubernetes.io/name: load-balancer-example name: hello-world spec: replicas: 5 selector: matchLabels: app.kubernetes.io/name: load-balancer-example template: metadata: labels: app.kubernetes.io/name: load-balancer-example spec: containers: - image: gcr.io/google-samples/node-hello:1.0 name: hello-world ports: - containerPort: 8080 EOF
- Pod を取得してみる。 READY になるまでは少し時間がかかる。
$ kubectl get pod NAME READY STATUS RESTARTS AGE hello-world-bbbb4c85d-572xj 1/1 Running 0 83s hello-world-bbbb4c85d-bdwgb 1/1 Running 0 83s hello-world-bbbb4c85d-fhx9z 1/1 Running 0 83s hello-world-bbbb4c85d-gc5rb 1/1 Running 0 83s hello-world-bbbb4c85d-gndwl 1/1 Running 0 83s
- 現在の Service を確認。
$ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 21m
- 公開するための Service を作成する。
kubectl expose deployment hello-world --type=LoadBalancer --name=my-service
- Service が追加されているのを確認。
$ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 23m my-service LoadBalancer 10.104.220.75 192.168.1.2 8080:31116/TCP 2s
- Service の詳細を取得。
$ kubectl describe services my-service Name: my-service Namespace: default Labels: app.kubernetes.io/name=load-balancer-example Annotations: <none> Selector: app.kubernetes.io/name=load-balancer-example Type: LoadBalancer IP: 10.100.88.21 LoadBalancer Ingress: 172.17.255.1 Port: <unset> 8080/TCP TargetPort: 8080/TCP NodePort: <unset> 30268/TCP Endpoints: 10.244.1.3:8080,10.244.1.4:8080,10.244.2.2:8080 + 2 more... Session Affinity: None External Traffic Policy: Cluster Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal IPAllocated 2m25s metallb-controller Assigned IP "172.17.255.1" Normal nodeAssigned 2m24s metallb-speaker announcing from node "kind1-worker"
- Port Forward で確認。
kubectl port-forward svc/my-service 8080:8080open http://localhost:8080
- 後始末
kubectl delete services my-service kubectl delete deployment hello-worldKubernetes のチュートリアルをやってみる (2)
kubectl apply -f - -o yaml << 'EOF' apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: Deployment metadata: name: redis-master labels: app: redis spec: selector: matchLabels: app: redis role: master tier: backend replicas: 1 template: metadata: labels: app: redis role: master tier: backend spec: containers: - name: master image: k8s.gcr.io/redis:e2e # or just image: redis resources: requests: cpu: 100m memory: 100Mi ports: - containerPort: 6379 EOF
- Redis Master の Service を作成。
kubectl apply -f - -o yaml << 'EOF' apiVersion: v1 kind: Service metadata: name: redis-master labels: app: redis role: master tier: backend spec: ports: - port: 6379 targetPort: 6379 selector: app: redis role: master tier: backend EOF
- Redis Slave の Pod をデプロイ。
kubectl apply -f - -o yaml << 'EOF' apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: Deployment metadata: name: redis-slave labels: app: redis spec: selector: matchLabels: app: redis role: slave tier: backend replicas: 2 template: metadata: labels: app: redis role: slave tier: backend spec: containers: - name: slave image: gcr.io/google_samples/gb-redisslave:v3 resources: requests: cpu: 100m memory: 100Mi env: - name: GET_HOSTS_FROM value: dns # Using `GET_HOSTS_FROM=dns` requires your cluster to # provide a dns service. As of Kubernetes 1.3, DNS is a built-in # service launched automatically. However, if the cluster you are using # does not have a built-in DNS service, you can instead # access an environment variable to find the master # service's host. To do so, comment out the 'value: dns' line above, and # uncomment the line below: # value: env ports: - containerPort: 6379 EOF
- Redis Slave の Service を作成。
kubectl apply -f - -o yaml << 'EOF' apiVersion: v1 kind: Service metadata: name: redis-slave labels: app: redis role: slave tier: backend spec: ports: - port: 6379 selector: app: redis role: slave tier: backend EOF
- フロントエンドをデプロイ。
kubectl apply -f - -o yaml << 'EOF' apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: Deployment metadata: name: frontend labels: app: guestbook spec: selector: matchLabels: app: guestbook tier: frontend replicas: 3 template: metadata: labels: app: guestbook tier: frontend spec: containers: - name: php-redis image: gcr.io/google-samples/gb-frontend:v4 resources: requests: cpu: 100m memory: 100Mi env: - name: GET_HOSTS_FROM value: dns # Using `GET_HOSTS_FROM=dns` requires your cluster to # provide a dns service. As of Kubernetes 1.3, DNS is a built-in # service launched automatically. However, if the cluster you are using # does not have a built-in DNS service, you can instead # access an environment variable to find the master # service's host. To do so, comment out the 'value: dns' line above, and # uncomment the line below: # value: env ports: - containerPort: 80 EOF
type
をLoadBalancer
に変更して、フロントエンドの Service を作成。kubectl apply -f - -o yaml << 'EOF' apiVersion: v1 kind: Service metadata: name: frontend labels: app: guestbook tier: frontend spec: # comment or delete the following line if you want to use a LoadBalancer # type: NodePort # if your cluster supports it, uncomment the following to automatically create # an external load-balanced IP for the frontend service. type: LoadBalancer ports: - port: 80 selector: app: guestbook tier: frontend EOF
- Port Forward して確認。
kubectl port-forward svc/frontend 8080:80open http://localhost:8080
- 出た。
- 後始末。
kubectl delete deployment -l app=redis kubectl delete service -l app=redis kubectl delete deployment -l app=guestbook kubectl delete service -l app=guestbookKubernetes のチュートリアルをやってみる (3)
cat <<EOF >./kustomization.yaml secretGenerator: - name: mysql-pass literals: - password=YOUR_PASSWORD EOF
- mysql-deployment.yaml を作成
cat <<EOF >./mysql-deployment.yaml apiVersion: v1 kind: Service metadata: name: wordpress-mysql labels: app: wordpress spec: ports: - port: 3306 selector: app: wordpress tier: mysql clusterIP: None --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mysql-pv-claim labels: app: wordpress spec: accessModes: - ReadWriteOnce resources: requests: storage: 20Gi --- apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: Deployment metadata: name: wordpress-mysql labels: app: wordpress spec: selector: matchLabels: app: wordpress tier: mysql strategy: type: Recreate template: metadata: labels: app: wordpress tier: mysql spec: containers: - image: mysql:5.6 name: mysql env: - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef: name: mysql-pass key: password ports: - containerPort: 3306 name: mysql volumeMounts: - name: mysql-persistent-storage mountPath: /var/lib/mysql volumes: - name: mysql-persistent-storage persistentVolumeClaim: claimName: mysql-pv-claim EOF
- wordpress-deployment.yaml を作成。
cat <<EOF >./wordpress-deployment.yaml apiVersion: v1 kind: Service metadata: name: wordpress labels: app: wordpress spec: ports: - port: 80 selector: app: wordpress tier: frontend type: LoadBalancer --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: wp-pv-claim labels: app: wordpress spec: accessModes: - ReadWriteOnce resources: requests: storage: 20Gi --- apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: Deployment metadata: name: wordpress labels: app: wordpress spec: selector: matchLabels: app: wordpress tier: frontend strategy: type: Recreate template: metadata: labels: app: wordpress tier: frontend spec: containers: - image: wordpress:4.8-apache name: wordpress env: - name: WORDPRESS_DB_HOST value: wordpress-mysql - name: WORDPRESS_DB_PASSWORD valueFrom: secretKeyRef: name: mysql-pass key: password ports: - containerPort: 80 name: wordpress volumeMounts: - name: wordpress-persistent-storage mountPath: /var/www/html volumes: - name: wordpress-persistent-storage persistentVolumeClaim: claimName: wp-pv-claim EOF
- kustomization.yaml に追記する。
cat <<EOF >>./kustomization.yaml resources: - mysql-deployment.yaml - wordpress-deployment.yaml EOF
- kustomization.yaml を含むディレクトリからリソースを適用する。
kubectl apply -k ./
- 下記リソースが作成された情報が出力される。
secret/mysql-pass-bk8h8tgt5t created service/wordpress-mysql created service/wordpress created deployment.apps/wordpress-mysql created deployment.apps/wordpress created persistentvolumeclaim/mysql-pv-claim created persistentvolumeclaim/wp-pv-claim created
- Service と Persistent Volume Claims を確認。
kubectl get pvc kubectl get svc
- Port Forward で WordPress を確認。
kubectl port-forward svc/wordpress 8080:80
- 動いてる。
以上。
とりあえず、基本は動いたと言えるのかな。
あと、 helm と operator もやってみないと。(追記) kubernetes dashboard を入れる
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v1.10.1/src/deploy/recommended/kubernetes-dashboard.yaml
kubectl proxyopen http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/
- ログインのための token を取得する。下記を参考にさせてもらいました。
kubectl get secret -n kube-system
deployment-controller-token-
で始まるものを指定する。kubectl describe secret deployment-controller-token-vtddf -n kube-system
- token でログインする。
- 投稿日:2019-10-24T00:21:08+09:00
ECS(Fargate)+APIGateway(Cognito認証)でAPIサーバーを構築する
前提条件/構成条件
- APIサーバーはNginx + Golang
- ユーザー情報はCognitoに格納されている
- ユーザー認証をAPIGatewayのAuthorizerで行なう
- CloudFront → API Gateway → Fargate
※AWSの設定作業はコンソールより手作業で行ないます
infra as codeはありません構成図
コンテナの準備
以下のDockerfileを準備します。
配置場所は当記事では{ルートdir}/release/app/
および{ルートdir}/release/nginx/
配下としています。まずはGoで作成するアプリケーションです。
FROM golang:1.13.0-alpine as builder ENV ROOT_PATH /go/src/github.com/xxx/yyy WORKDIR $ROOT_PATH RUN apk add --no-cache alpine-sdk git RUN addgroup -g 10001 -S admin \ && adduser -u 10001 -G admin -S admin # modules COPY go.mod go.sum ./ RUN go mod download # application COPY . . # build RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o /go/bin/app && \ chmod 755 /go/bin/app && \ chown admin:admin /go/bin/app FROM scratch COPY --from=builder /etc/group /etc/group COPY --from=builder /etc/passwd /etc/passwd COPY --from=builder /go/bin/app /go/bin/app USER admin EXPOSE 9000 CMD ["/go/bin/app"]アプリケーションはとりあえず以下な感じでよいでしょう。
go.modとgo.sumも配置しておいてください。main.gopackage main import ( "github.com/labstack/echo" "net/http" ) func main() { e := echo.New() e.GET("/", func(c echo.Context) error { return c.String(http.StatusOK, "Hello World!") }) e.Logger.Fatal(e.Start(":9000")) }続いてNginxです。
FROM nginx:1.17.4-alpine ADD ./release/nginx/nginx.conf /etc/nginx/conf.d/default.confFargateは
127.0.0.1
で待ち受けます。
PortはGo側で設定したもの(9000
)を設定します。nginx.confserver { listen 80 default_server; listen [::]:80 default_server; include /etc/nginx/default.d/*.conf; location / { proxy_pass http://127.0.0.1:9000; } }AWS構築手順
VPC
VPCの作成
特に言及することはありません。
既に利用されているものがあればそのまま使いましょうサブネットの作成
プライベートサブネットで作成します。
Fargateの場合はサーバーに固定IPを割り振りませんので、NATゲートウェイを設定しておきます。当記事では以下の2つのCIDRブロックとしています。
- 10.1.12.0/24(ap-northeast-1a)
- 10.1.52.0/24(ap-northeast-1c)
後述するNATゲートウェイも作成する場合は、パブリックサブネットも一緒に作成しておきます。
NATゲートウェイ
NATゲートウェイを作成します。ElasticIPが必要になりますので新しく作成しましょう。
※既に利用されているものがあればそのまま使いましょう作成後は、先ほど作成したサブネットのルートテーブルに紐付けるのを忘れないようにしましょう。
※NATゲートウェイ自体が配置されているパブリックサブネットはインターネットゲートウェイが必要になります。
忘れないように設定しておきましょう。セキュリティグループ作成
続いてセキュリティグループを作成します。
このセキュリティグループはNLBからコンテナ(Nginx)へのアクセスで用いられます。
NLBについては後述します。以下のように先ほど作成したサブネットのCIDRブロックを記述して反映します。ロードバランサー
NLBの作成
APIGatewayからロードバランサーへルーティングする場合、VPCリンクで繋ぐ必要があります。
当記事ではAPIをVPCのプライベート空間(インターネットに公開しない)に配置するためNLBによる構築となります。スキームは
内部
、リスナーはTLS(セキュアTCP)
を選択します。
VPCは先ほど作成したVPCとサブネットを指定します。証明書
ACMに既に証明書が存在する場合はそのまま使いましょう。
当記事ではセキュリティポリシーをELBSecurityPolicy-TLS-1-2-2017-01
を選択しています。ターゲットグループの作成
新しくターゲットグループを作成します。
ターゲットの種類はIP
を選択します
次画面のターゲットの登録は、一旦スキップでOKです。ドメインの確保
NLBの作成が完了したらDNSを自身のドメインに設定します。
Route53を利用していればAliasレコードで登録できるかと思います。
当記事ではホスティングゾーンが別のためCNAMEで設定します。
※このドメインはサービスのエンドポイントではありません。
CloudFront→①→APIGateway→②→NLB の②にあたる部分です。
①については後ほど設定します。例: elb-origin.domain.com など
ECR
リポジトリの作成
ECRにリポジトリを作成しておきます。
当記事ではリポジトリは以下の名称にしています。
- {サービス名}/app
- {サービス名}/nginx
これでコンテナのURIが以下のようになると思います。
- {AWSのアカウントID}.dkr.ecr.ap-northeast-1.amazonaws.com/{サービス名}/app
- {AWSのアカウントID}.dkr.ecr.ap-northeast-1.amazonaws.com/{サービス名}/nginx
コンテナのプッシュ
当記事の初めに準備したコンテナをプッシュします。
※CI環境では自動プッシュ/デプロイを設定することになります。今回は最初だけ手動プッシュとします。まずはビルド
$ docker build --no-cache -t {appコンテナ名}:latest -f release/app/Dockerfile . $ docker build --no-cache -t {nginxコンテナ名}:latest -f release/nginx/Dockerfile .タグ付け
$ docker tag {appコンテナ名}:latest {ECRのコンテナURI}:latest $ docker tag {nginxコンテナ名}:latest {ECRのコンテナURI}:latestECRにログインします
※awscliが使えない場合はインストールしてください$ $(aws ecr get-login --no-include-email)プッシュします
$ docker push {ECRのコンテナURI(app)}:latest $ docker push {ECRのコンテナURI(nginx)}:latestECS
クラスターの作成
まずはクラスターを作成します。クラスターは単なる名前空間になります。
コンテナの設定、配置する数やスペックについては後述するサービス
、タスク
による定義で設定します。クラスターテンプレートは
ネットワーキングのみ
を指定して作成します。IAMロールの作成
次にIAMロールを作成します。2つ作成します
タスクロール
このロールはコンテナ内部でAWSへのAPIリクエストを行なうためにタスクで使用するものです。
例えばS3にアクセスが必要なアプリケーションの場合、S3へのアクセスが可能なロールを定義します。コンソールから作成する場合は、以下の項目を信頼されたエンティティとして作成します。
Elastic Container Service Task
タスク実行ロール
このロールはタスクのプルやログの発行などを管理するロールです。
AmazonECSTaskExecutionRolePolicy
というポリシーを付与して作成します。タスク定義の作成
コンソールから設定する場合、まず
タスクロール
とタスク実行ロール
を指定します。
先ほど作成したIAMロールを指定します。
タスク実行ロールにはAmazonECSTaskExecutionRolePolicy
がアタッチされているロールを指定します。タスクサイズはとりあえず最低スペック(メモリ0.5GB、CPUが0.25vCPU)にしておきます。
続いてコンテナの追加ですが、ECRにプッシュしてあるappコンテナとnginxコンテナを指定します。
ヘルスチェックやログ出力などの細かい設定はとりあえずデフォルトのまま進めます。nginxコンテナは、ポートマッピングを設定します。
80
を設定してください。appコンテナとnginxコンテナを設定できればタスク定義の作成を完了します。
サービスの作成
サービスはクラスターの中から作成することができます。
ステップ1 サービスの設定
ステップ1の
サービスの設定
については特に難しくはありません。
起動タイプをFARGATE
とし、タスク定義やクラスターは先ほど作成したものを選択します。
タスクの数は最低料金に抑えるため1
にしておきます。(商用では適切なタスク数を検討してください)
デプロイメントについては、当記事ではローリングアップデート
で進めます。ステップ2 ネットワーク構成
ステップ2のネットワーク構成です。
VPCとセキュリティグループは、先ほど作成したVPCとサブネット、セキュリティグループを選択します。
当記事では以下のサブネットを作成していました。
- 10.1.12.0/24
- 10.1.52.0/24
パブリックIPの自動割り当てについては
DISABLED
にしておきます。続いて、ロードバランサーの設定です。先ほど作成したNLBを指定します。
ターゲットグループ名も、先ほど作成しているので選択します。
サービスの検出(オプション)
という項目については当記事ではOFFで設定します。ステップ3 Auto Scaling(オプション)
ステップ3はAutoScaleingの設定です。
商用で利用する場合は必ず設定しましょう。当記事では設定せずに先に進みます。
API Gateway
VPCリンクの作成
APIGatewayよりVPCリンクを作成します。
ターゲットNLBを先ほど作成したNLBとします。APIGatewayの作成
APIGatewayを作成します。
メソッドの作成
一旦、
/
パスにGETメソッドを定義します。
統合タイプはVPCリンク
を選択し、先ほど作成したVPCリンクを設定します。
エンドポイントURLは、先ほど作成したNLBのAliasレコード(もしくはCNAME)を設定します。
ここはNLBでインポートしたACMに対応するドメインである必要があります。APIのデプロイ
メソッドが作成したらデプロイを行ないます。(コンソールから作業するとよく忘れます。。)
ステージ名は適当にdev
とでもしておきます。CloudFront
Create Distribution
CloudFrontを設定していきます。
各項目については以下の通りです。これ以外の項目については要件により適宜設定してください。
項目 内容 Origin Domain Name 先ほど作成したAPIGatewayのドメイン
例:xxxxx.execute-api.ap-northeast-1.amazonaws.com
(ステージ名は除く)Origin Path APIGatewayのステージ名
例:/dev
Origin Protocol Policy HTTPS Only Viewer Protocol Policy Redirect HTTP to HTTPS Allowed HTTP Methods GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE Alternate Domain Names(CNAMEs) 自身のサービス用ドメイン(APIのエンドポイント) SSL Certificate Custom SSL Certificate (example.com): (自身のACM証明書を選択) ここまで出来れば、自身のドメインでアクセスすることで
Hello World!
が表示されるはずです。Authorizerの設定
Cognito
前提条件として、Cognitoにエンドユーザー(当APIにアクセスするユーザー)の情報が格納されていることとします。
Cognitoの扱い方については当記事では対象外となります。APIGateway
新しいオーソライザーの作成
自身のAPIGatewayのメニューより、
オーソライザー
>新しいオーソライザーの作成
を選択します。
タイプをCognito
とし、ユーザープール名を指定します。
トークンのソースはAuthorization
とします。オーソライザーを作成したら、新しく
POST
のAPIおよびメソッドを作成します。
このAPIではCognitoより、認証されたユーザーの情報を受け取る検証をするためPOST
としています。オーソライザーは以下の
メソッドリクエスト
の認可
より設定します。次に
統合リクエスト
のマッピングテンプレート
を選択します。
以下の形式でCognitoからのパラメータ群を取得することができます。{ "sub": "$context.authorizer.claims.sub", "username": "$context.authorizer.claims['cognito:username']", "birthdate": "$context.authorizer.claims.birthdate", "gender": "$context.authorizer.claims.gender", ...(省略) }アプリケーション側(Go)では以下のように取得できるかと思います。
main.gopackage main import ( "github.com/labstack/echo" "net/http" ) type User struct { Sub string `json:"sub"` Username string `json:"username"` BirthDate string `json:"birthdate"` Gender string `json:"gender"` Name string `json:"name"` Locale string `json:"locale"` Email string `json:"email"` Picture string `json:"picture"` } func main() { e := echo.New() // ユーザーの取得 e.POST("/test", func(c echo.Context) error { user := new(User) if err := c.Bind(user); err != nil { return err } return c.JSON(http.StatusOK, user) }) e.Logger.Fatal(e.Start(":9000")) }CI構築
当記事ではCI環境をCodePipelineで行ないます。
CodeBuild
ビルドプロジェクトの作成
まずはCodeBuildを設定します。
送信元については、当記事ではGithubを設定しています。
ビルド環境についてはAmazon Linux 2
を選択します。(好きなものでいいです)Buildspacについては
buildspec.yaml
を配置する予定のパスを指定します。buildspec.yamlの配置
内容は以下の通りです。
buildspec.yamlversion: 0.2 phases: install: runtime-versions: docker: 18 commands: pre_build: commands: - $(aws ecr get-login --no-include-email) build: commands: - docker build --no-cache -t {appコンテナ名}:latest -f release/app/Dockerfile . - docker build --no-cache -t {nginxコンテナ名}:latest -f release/nginx/Dockerfile . - docker tag {appコンテナ名}:latest {appのコンテナURI}:latest - docker tag {nginxコンテナ名}:latest {nginxのコンテナURI}:latest post_build: commands: - docker push {appのコンテナURI}:latest - docker push {nginxのコンテナURI}:latest - | printf '[{"name":"app","imageUri":"%s"},{"name":"nginx","imageUri":"%s"}]' \ {appのコンテナURI}:latest {nginxのコンテナURI}:latest > imagedefinitions.jsonCodeDeploy
※CodeDeployについてはBlue/Greenデプロイを利用する場合のみ設定します。
当記事ではローリングアップデートで進めるためこちらの作成は不要です。CodePipeline
新規のパイプラインを作成する
続いてCodePipelineを作成します。
ソースステージについては、当記事ではGithubを選択しています。ビルドステージについては先ほど作成したプロジェクトを指定します。
デプロイステージについては
Amazon ECS
を指定します。
Blue/Greenデプロイの場合は別途Amazon ECS(ブルー/グリーン)
を指定します。
イメージ定義ファイルはimagedefinitions.json
です。CodeBuildのbuildspec.yaml
で出力するJSONです。APIGatewayのデプロイ
APIGatewayについても、今後手動で設定していくのが辛いのでCI環境内でデプロイしたいものです。
今回は、CodeBuild上でデプロイします。
buildspec.yaml
のpost_build
(ビルド完了後)に以下のコマンドを追記します
CodeBuiildの実行ロールにAPIGatewayの実行権限を付与するのを忘れないでください。buildspec.yaml# API Gateway Deploy - aws apigateway put-rest-api --rest-api-id {APIGatewayのID} --mode overwrite --body file://openapi.yaml - aws apigateway create-deployment --rest-api-id {APIGatewayのID} --stage-name {APIGatewayのステージ名}ここで
openapi.yaml
というファイルが登場しています。
こちらは、APIGatewayの構成を記述したOpenAPI(もしくはSwagger)形式のyamlとなります。
APIGatewayのステージ
>エクスポート
よりエクスポートができ、追記したものをインポートしデプロイすることができます。アプリケーション側でAPI、メソッド等を追記したらこのファイルも更新しましょう。
openapi.yamlopenapi: "3.0.1" info: title: "{Your API Title}" version: "xxx" servers: - url: "https://example.execute-api.ap-northeast-1.amazonaws.com/{basePath}" variables: basePath: default: "/dev" paths: /: get: responses: 200: description: "200 response" content: application/json: schema: $ref: "#/components/schemas/Empty" x-amazon-apigateway-integration: uri: "https://elb-origin.domain.com/" responses: default: statusCode: "200" passthroughBehavior: "when_no_match" connectionType: "VPC_LINK" connectionId: "xxx" httpMethod: "GET" type: "http" components: schemas: Empty: title: "Empty Schema" type: "object"以上で全行程が完了です。