20191024のdockerに関する記事は7件です。

ECRでコンテナ脆弱性診断が実装されていたので試してみた

ECRでスキャン?

今日の業務中にふとECR画面見たら、「スキャン」の表示を見つける。
コンテナの脆弱性診断が実装されてる?
ん?こんなのあったっけ。見落としていただけかな。ということで触ってみた。

触ってみた

リポジトリ作成

1.png
「プッシュ時にスキャン」画面メニューがある。とりあえず、リポジトリ作成してみる。

2.png
なるほど、docker pushでコンテナ登録する時にスキャンが走るようだ。有効にしてみる。

3.png
有効になった。

コンテナを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する。

スキャン完了。そして脆弱性確認へ・・・

4.png

スキャンは完了!脆弱性があるようなので、詳細を見てみる。

5.png
概要で脆弱性の件数とパッケージ毎の脆弱性説明が表示される。
例では重要度がCriticalはないが、HighMediumがそれぞれ指摘された。
名前がリンクになっているのでクリックすると・・・

6.png
指摘毎に対処ページが表示される。便利。

脆弱性の対応をしてみる。

パッケージのバージョンアップをすることで、脆弱性指摘が減るか確認してみる。

FROM centos:latest
LABEL maintainer "portfield"

RUN yum -y update && yum clean all

OSアップデートする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する。

再びスキャン完了。脆弱性確認へ・・・

7.png
上がOSアップデート前、下がOSアップデート後のDockerイメージ。
脆弱性件数が減っていることから、OSアップデートにより、脆弱性が減ったことが分かる。
詳細も見てみよう。

8.png
OSアップデートだけでは対処出来なかった脆弱性が表示される。
名前のリンクと説明を頼りに対応しよう。今回は対応しない。。

スキャンを有効にしていなくてもOK。

9.png
手動実行出来るので、大丈夫。

10.png
このように進行中となり、最終的に完了する。

まとめ

・以上のように、リポジトリの設定を有効にするだけで、簡単に使用することが出来た。
CI/CDの一環でソース更新時にDockerイメージを自動作成し、ECRにpushしているケースも多いと思うので、便利に使用出来そう。
・というか、元々あったのだろうか???ドキュメント探しても見つからない。。
裏で何をベースにスキャンしているとか、情報があったら欲しいです。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ECRでコンテナ脆弱性診断が標準実装されていたので試してみた

※2019/10/25(金)追記:
本機能は使用出来なくなっているようです。

ECRでスキャン?

今日の業務中にふとECR画面見たら、「スキャン」の表示を見つける。
コンテナの脆弱性診断が実装されてる?
ん?こんなのあったっけ。見落としていただけかな。ということで触ってみた。

リポジトリ作成

1.png
「プッシュ時にスキャン」画面メニューがある。とりあえず、リポジトリ作成してみる。

2.png
なるほど、docker pushでコンテナ登録する時にスキャンが走るようだ。有効にしてみる。

3.png
有効になった。

コンテナを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する。

スキャン完了。そして脆弱性確認へ・・・

4.png

スキャンは完了!脆弱性があるようなので、詳細を見てみる。

5.png
概要で脆弱性の件数とパッケージ毎の脆弱性説明が表示される。
例では重要度がCriticalはないが、HighMediumがそれぞれ指摘された。
名前がリンクになっているのでクリックすると・・・

6.png
指摘毎に対処ページが表示される。便利。

脆弱性の対応をしてみる。

パッケージのバージョンアップをすることで、脆弱性指摘が減るか確認してみる。

FROM centos:latest
LABEL maintainer "portfield"

RUN yum -y update && yum clean all

OSアップデートする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する。

再びスキャン完了。脆弱性確認へ・・・

7.png
上がOSアップデート前、下がOSアップデート後のDockerイメージ。
脆弱性件数が減っていることから、OSアップデートにより、脆弱性が減ったことが分かる。
詳細も見てみよう。

8.png
OSアップデートだけでは対処出来なかった脆弱性が表示される。
名前のリンクと説明を頼りに対応しよう。今回は対応しない。。

スキャンを有効にしていなくてもOK。

9.png
手動実行出来るので、大丈夫。

10.png
このように進行中となり、最終的に完了する。

まとめ

・以上のように、リポジトリの設定を有効にするだけで、簡単に使用することが出来た。
CI/CDの一環でソース更新時にDockerイメージを自動作成し、ECRにpushしているケースも多いと思うので、便利に使用出来そう。
・というか、元々あったのだろうか???ドキュメント探しても見つからない。。
裏で何をベースにスキャンしているとか、情報があったら欲しいです。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コンテナ基礎 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からチャーハンを作るようなもの。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

今更だけど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 で実行された内容

  1. adminユーザー(私)が新規ユーザー(犯人)を作成。
  2. 作成したユーザーに最低限の権利を付与。
  3. 新規ユーザーがログイン。
  4. プロジェクトの作成権限があるクラスターでコンテナを作成。
  5. 以降は上記のdockerコマンド実行内容と同様。

以下Rancherの操作画面を使って説明いたします。

1.adminユーザー(私)が新規ユーザー(犯人)を作成。

adminでログインし、「グローバル」タブの「ユーザー」を選択しユーザーを追加する。
図1.png

ユーザーの内容は以下で作りました。
ユーザー名: waruihito
パスワード: waruihito
表示名: waruihito
グローバル権限: 一般ユーザー
図2.png

2.作成したユーザーに最低限の権利を付与。

クラスターのメンバーにも新規ユーザーを追加。(今回のクラスタ名は「sandbox」)
今回は最低限の権限として「Create Project」のみを付与します。
図4.png

3.新規ユーザーでログイン。

作成したユーザーでログインし直すと、権限が振られているクラスターのみが表示されます。
図5.png

「Create Project」権限を持っているので、新規プロジェクト「warudakumi」を作成します。
図6.png

4.クラスターでコンテナを作成。

先程の「warudakumi」プロジェクトで、「ワークロード」から乗っ取り用のコンテナをデプロイしていきます。
図7.png

5.以降は上記のdockerコマンド実行内容と同様。

ボリュームにノード上の "/" パスを指定します。
図8.png

作成したコンテナ(ポッド)でシェルを実行します。(Rancherではプルダウンから選択して起動できます。)
図9.png

コマンドを実行して乗っ取りができました。
図10.png

図11.png

学んだこと

Docker単体で運用するときには結構気を使っている部分ですし、公式のリファレンスにも「ここに気をつけて!」みたいな文言は書いてあります。
しかしk8sやRancherなどで運用する際には「それは知ってて当然だよね?」という暗黙のルールのもと構築をしてしまうことで、このような初歩的な脆弱性を疲れることになりました。(いや口で言ってくれたら分かるよ?わざわざsshd_config書き換える必要ある?印象には残ったけどね!)

基本大事!効率的にするためであって、横着してもいいわけではないということだと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerのホスト側でデータベース復元

環境

Docker

準備

直接パスワード入れるのはアレなので、
設定ファイル作っておく。

また、docker-compose.ymlでボリュームとか設定する。
このデータ共有されてるホスト側のパスを後ほど指定する。

シェル

doHukugen.sh
docker 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でコンテナを操作するために必要なオプション。
これはもちろん必要なので今回はこのオプションだけ。

結果

このシェルスクリプトを実行してみると、指定したダンプファイルの状態にデータが戻った

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 version

Kubernetes cluster を作成

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/config

MetalLB のデプロイ

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
EOF

Kubernetes のチュートリアルをやってみる

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:8080
open http://localhost:8080
  • 後始末
kubectl delete services my-service
kubectl delete deployment hello-world

Kubernetes のチュートリアルをやってみる (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
  • typeLoadBalancer に変更して、フロントエンドの 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:80
open http://localhost:8080
  • 出た。

image.png

  • 後始末。
kubectl delete deployment -l app=redis
kubectl delete service -l app=redis
kubectl delete deployment -l app=guestbook
kubectl delete service -l app=guestbook

Kubernetes のチュートリアルをやってみる (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
  • 動いてる。

image.png

image.png

image.png

image.png

以上。
とりあえず、基本は動いたと言えるのかな。

あと、 helm と operator もやってみないと。

(追記) kubernetes dashboard を入れる

kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v1.10.1/src/deploy/recommended/kubernetes-dashboard.yaml
kubectl proxy
open http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/

image.png

kubectl get secret -n kube-system
  • deployment-controller-token- で始まるものを指定する。
kubectl describe secret deployment-controller-token-vtddf -n kube-system
  • token でログインする。

image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ECS(Fargate)+APIGateway(Cognito認証)でAPIサーバーを構築する

前提条件/構成条件

  • APIサーバーはNginx + Golang
  • ユーザー情報はCognitoに格納されている
  • ユーザー認証をAPIGatewayのAuthorizerで行なう
  • CloudFront → API Gateway → Fargate

※AWSの設定作業はコンソールより手作業で行ないます
infra as codeはありません

構成図

0a352ed0c87e45014ce1-001.png

コンテナの準備

以下の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.go
package 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.conf

Fargateは127.0.0.1で待ち受けます。
PortはGo側で設定したもの(9000)を設定します。

nginx.conf
server {
    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ゲートウェイも作成する場合は、パブリックサブネットも一緒に作成しておきます。

スクリーンショット 2019-10-21 11.11.28.png

NATゲートウェイ

NATゲートウェイを作成します。ElasticIPが必要になりますので新しく作成しましょう。
 ※既に利用されているものがあればそのまま使いましょう

スクリーンショット 2019-10-21 11.14.58.png

作成後は、先ほど作成したサブネットのルートテーブルに紐付けるのを忘れないようにしましょう。

スクリーンショット 2019-10-21 11.15.56.png

※NATゲートウェイ自体が配置されているパブリックサブネットはインターネットゲートウェイが必要になります。
 忘れないように設定しておきましょう。

セキュリティグループ作成

続いてセキュリティグループを作成します。
このセキュリティグループはNLBからコンテナ(Nginx)へのアクセスで用いられます。
NLBについては後述します。以下のように先ほど作成したサブネットのCIDRブロックを記述して反映します。

スクリーンショット 2019-10-21 11.20.45.png

ロードバランサー

NLBの作成

APIGatewayからロードバランサーへルーティングする場合、VPCリンクで繋ぐ必要があります。
当記事ではAPIをVPCのプライベート空間(インターネットに公開しない)に配置するためNLBによる構築となります。

スキームは内部、リスナーはTLS(セキュアTCP)を選択します。
VPCは先ほど作成したVPCとサブネットを指定します。

スクリーンショット 2019-10-21 11.41.13.png

証明書

ACMに既に証明書が存在する場合はそのまま使いましょう。
当記事ではセキュリティポリシーをELBSecurityPolicy-TLS-1-2-2017-01を選択しています。

スクリーンショット 2019-10-21 11.45.20.png

ターゲットグループの作成

新しくターゲットグループを作成します。
ターゲットの種類はIPを選択します
次画面のターゲットの登録は、一旦スキップでOKです。

スクリーンショット 2019-10-21 11.47.48.png

ドメインの確保

NLBの作成が完了したらDNSを自身のドメインに設定します。
Route53を利用していればAliasレコードで登録できるかと思います。
当記事ではホスティングゾーンが別のためCNAMEで設定します。
 ※このドメインはサービスのエンドポイントではありません。
  CloudFront→①→APIGateway→②→NLB の②にあたる部分です。
  ①については後ほど設定します。

例: elb-origin.domain.com など

ECR

リポジトリの作成

ECRにリポジトリを作成しておきます。

スクリーンショット 2019-10-21 12.22.39.png

当記事ではリポジトリは以下の名称にしています。

  • {サービス名}/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}:latest

ECRにログインします
 ※awscliが使えない場合はインストールしてください

$ $(aws ecr get-login --no-include-email)

プッシュします

$ docker push {ECRのコンテナURI(app)}:latest
$ docker push {ECRのコンテナURI(nginx)}:latest

ECS

クラスターの作成

まずはクラスターを作成します。クラスターは単なる名前空間になります。
コンテナの設定、配置する数やスペックについては後述するサービスタスクによる定義で設定します。

クラスターテンプレートはネットワーキングのみを指定して作成します。

スクリーンショット 2019-10-21 12.09.59.png

IAMロールの作成

次にIAMロールを作成します。2つ作成します

タスクロール

このロールはコンテナ内部でAWSへのAPIリクエストを行なうためにタスクで使用するものです。
例えばS3にアクセスが必要なアプリケーションの場合、S3へのアクセスが可能なロールを定義します。

コンソールから作成する場合は、以下の項目を信頼されたエンティティとして作成します。
Elastic Container Service Task

スクリーンショット 2019-10-21 12.18.14.png

タスク実行ロール

このロールはタスクのプルやログの発行などを管理するロールです。
AmazonECSTaskExecutionRolePolicyというポリシーを付与して作成します。

スクリーンショット 2019-10-21 16.11.36.png

タスク定義の作成

コンソールから設定する場合、まずタスクロールタスク実行ロールを指定します。
先ほど作成したIAMロールを指定します。
タスク実行ロールにはAmazonECSTaskExecutionRolePolicyがアタッチされているロールを指定します。

スクリーンショット 2019-10-21 14.12.55.png

タスクサイズはとりあえず最低スペック(メモリ0.5GB、CPUが0.25vCPU)にしておきます。

続いてコンテナの追加ですが、ECRにプッシュしてあるappコンテナとnginxコンテナを指定します。
ヘルスチェックやログ出力などの細かい設定はとりあえずデフォルトのまま進めます。

nginxコンテナは、ポートマッピングを設定します。80を設定してください。

スクリーンショット 2019-10-21 14.19.20.png

appコンテナとnginxコンテナを設定できればタスク定義の作成を完了します。

サービスの作成

サービスはクラスターの中から作成することができます。

スクリーンショット 2019-10-21 14.33.09.png

ステップ1 サービスの設定

ステップ1のサービスの設定については特に難しくはありません。
起動タイプをFARGATEとし、タスク定義やクラスターは先ほど作成したものを選択します。
タスクの数は最低料金に抑えるため1にしておきます。(商用では適切なタスク数を検討してください)
デプロイメントについては、当記事ではローリングアップデートで進めます。

スクリーンショット 2019-10-21 14.38.28.png

ステップ2 ネットワーク構成

ステップ2のネットワーク構成です。
VPCとセキュリティグループは、先ほど作成したVPCとサブネット、セキュリティグループを選択します。
当記事では以下のサブネットを作成していました。

  • 10.1.12.0/24
  • 10.1.52.0/24

パブリックIPの自動割り当てについてはDISABLEDにしておきます。

続いて、ロードバランサーの設定です。先ほど作成したNLBを指定します。
ターゲットグループ名も、先ほど作成しているので選択します。

スクリーンショット 2019-10-21 14.44.22.png

サービスの検出(オプション)という項目については当記事ではOFFで設定します。

ステップ3 Auto Scaling(オプション)

ステップ3はAutoScaleingの設定です。
商用で利用する場合は必ず設定しましょう。

当記事では設定せずに先に進みます。

API Gateway

VPCリンクの作成

APIGatewayよりVPCリンクを作成します。
ターゲットNLBを先ほど作成したNLBとします。

スクリーンショット 2019-10-21 15.12.50.png

APIGatewayの作成

APIGatewayを作成します。

スクリーンショット 2019-10-21 15.36.57.png

メソッドの作成

一旦、/パスにGETメソッドを定義します。
統合タイプはVPCリンクを選択し、先ほど作成したVPCリンクを設定します。
エンドポイントURLは、先ほど作成したNLBのAliasレコード(もしくはCNAME)を設定します。
ここはNLBでインポートしたACMに対応するドメインである必要があります。

スクリーンショット 2019-10-21 16.03.17.png

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とします。

スクリーンショット 2019-10-23 12.16.30.png

オーソライザーを作成したら、新しくPOSTのAPIおよびメソッドを作成します。
このAPIではCognitoより、認証されたユーザーの情報を受け取る検証をするためPOSTとしています。

オーソライザーは以下のメソッドリクエスト認可より設定します。

スクリーンショット 2019-10-23 12.19.32.png

次に統合リクエストマッピングテンプレートを選択します。
以下の形式でCognitoからのパラメータ群を取得することができます。

スクリーンショット 2019-10-23 12.28.34.png

{
  "sub": "$context.authorizer.claims.sub",
  "username": "$context.authorizer.claims['cognito:username']",
  "birthdate": "$context.authorizer.claims.birthdate",
  "gender": "$context.authorizer.claims.gender",
  ...(省略)
}

アプリケーション側(Go)では以下のように取得できるかと思います。

main.go
package 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を選択します。(好きなものでいいです)

スクリーンショット 2019-10-21 17.47.34.png

Buildspacについてはbuildspec.yamlを配置する予定のパスを指定します。

スクリーンショット 2019-10-21 17.55.23.png

buildspec.yamlの配置

内容は以下の通りです。

buildspec.yaml
version: 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.json

CodeDeploy

※CodeDeployについてはBlue/Greenデプロイを利用する場合のみ設定します。
 当記事ではローリングアップデートで進めるためこちらの作成は不要です。

CodePipeline

新規のパイプラインを作成する

続いてCodePipelineを作成します。
ソースステージについては、当記事ではGithubを選択しています。

スクリーンショット 2019-10-23 11.26.28.png

ビルドステージについては先ほど作成したプロジェクトを指定します。

スクリーンショット 2019-10-23 11.28.12.png

デプロイステージについてはAmazon ECSを指定します。
Blue/Greenデプロイの場合は別途Amazon ECS(ブルー/グリーン)を指定します。
イメージ定義ファイルはimagedefinitions.jsonです。CodeBuildのbuildspec.yamlで出力するJSONです。

スクリーンショット 2019-10-23 11.30.10.png

APIGatewayのデプロイ

APIGatewayについても、今後手動で設定していくのが辛いのでCI環境内でデプロイしたいものです。
今回は、CodeBuild上でデプロイします。
buildspec.yamlpost_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.yaml
openapi: "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"

以上で全行程が完了です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む