Upgrade to Pro — share decks privately, control downloads, hide ads and more …

新卒1年目のSREがコンテナをデプロイできるようになるまでの道のり [JAWS DAYS 2019]

新卒1年目のSREがコンテナをデプロイできるようになるまでの道のり [JAWS DAYS 2019]

気づいたらコンテナ環境を運用することになっていた新人SREの原田くんが、Dockerでコンテナイメージをビルドするところから始まり、AWS上で実際にコンテナを動かせるようになるまでの道のりを紹介します。そもそもなぜコンテナなのか?動かしたあとのCICDをどうするかなども取り上げます。またEKSやKubernetesの概念についても合わせて解説します。

補足や当日の様子など書いた登壇レポート記事はこちら ↓
https://medium.com/eureka-engineering/deploy-docker-kubernetes-newgrad-sre-journey-1fb3f017eed7

Daichi Harada

February 23, 2019
Tweet

More Decks by Daichi Harada

Other Decks in Technology

Transcript

  1. Daichi Harada & Jun Sakata JAWS DAYS 2019 2019. 02

    . 23 新卒1年目のSREが コンテナをデプロイできるよう になるまでの道のり
  2. DAICHI HARADA - @kai_ten_sushi / @dharada1 - SRE at eureka,

    Inc. - 北海道から遥々上京 - 気づいたら SRE に配属されてしまった! - インフラ経験ゼロからのスタート - Like - #CloudFront - #CostExplorer
  3. JUN SAKATA - @sakajunquality - 別のクラウドのエキスパート - SRE at Ubie

    Inc. - Former SRE at eureka Inc. - Like - #CodeBuild - #Aurora
  4. Agenda - Part 1 - Docker - Kubernetes - Part

    2 - Container Services on AWS - ECR / ECS / EKS / Fargate - EKS を使ってみた - Part 3 - CI/CD
  5. こんな感じのコードですね // main.go package main import ( "fmt" "net/http" )

    func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, World") } func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8888", nil) }
  6. 8888でhttpリッスンして // main.go package main import ( "fmt" "net/http" )

    func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, World") } func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8888", nil) }
  7. “Hello, World” と表示する // main.go package main import ( "fmt"

    "net/http" ) func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, World") } func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8888", nil) }
  8. // Dockerfile FROM golang:1.11 WORKDIR /go/src/app COPY . . CMD

    ["go", “run”, “main.go”] こんな感じですか?
  9. // Dockerfile FROM golang:1.11 WORKDIR /go/src/app COPY . . RUN

    go get -d -v ./... RUN go install -v ./... CMD ["app"] どや
  10. マルチステージビルドを使うとよいよ // Dockerfile FROM golang:1.11-alpine WORKDIR /go/src/app COPY . .

    RUN go get -d -v ./... RUN go install -v ./... FROM alpine COPY --from=0 /go/src/app /usr/local/bin/app RUN apk --no-cache add ca-certificates CMD ["app"]
  11. イメージのサイズを比べてみよう $ docker images | grep my-app my-app latest 9ba39c3836a9

    38 minutes ago 801MB my-app-alpine latest 3880eef6c224 5 minutes ago 401MB my-app-multistage latest ce94afe10faf 1 second ago 11.6MB
  12. Dockerfileとは・・・ - Docker イメージの構成内容を記したもの - 専用の DSL で記述する - FROM

    でベースイメージを指定し - RUN でビルドなどのコマンドを実行
  13. Docker Hubとは・・・ - Docker 社が運営する Docker レジス トリ - FROM

    golang:1.11 や FROM alpine など書いて Pull される Image は Docker Hub にホストされている https://hub.docker.com/
  14. - クラウド - ECR ( AWS ) - GCR (

    Google ) - ACR ( Azure ) - ... - OSS - Harbor - .... DockerHub以外のレジストリ
  15. $ docker image --help Usage: docker image COMMAND Manage images

    Commands: build Build an image from a Dockerfile history Show the history of an image import Import the contents from a tarball to create a filesystem image inspect Display detailed information on one or more images load Load an image from a tar archive or STDIN ls List images prune Remove unused images pull Pull an image or a repository from a registry push Push an image or a repository to a registry ... テキトウに--helpすればいいのか
  16. VM vs Container - VM - ホスト上でハードウェアが仮想化され、その上 でゲスト OS が動作

    - 必要とする以上のリソースを割り当ててしま い、オーバーヘッドが大きい - OS を立ち上げるため起動時間がかかる - Container - コンテナはホスト OS のカーネルを利用 - リソースのオーバーヘッドが小さい - 起動が速い
  17. docker-compose - 複数のコンテナの立ち上げを yaml で記述 - ローカル開発環境で主に利用 - ミドルウェアのコンテナを docker-compose

    で立ち上げる - MySQL - Elasticsearch - DynamoDB Local - ローカルの go アプリケーションから各コン テナを利用 - いちいち brew install とかしなくていいの で開発者は楽 // docker-compose.yml services: mysql: image: mysql:5.7 environment: MYSQL_USER: datch_datch_go MYSQL_PASSWORD: fake_password ports: - "3306:3306" volumes: - .local/mysql5.7:/var/lib/mysql redis: image: redis:3.2-alpine ports: - "6379:6379" redis-cluster: image: "grokzen/redis-cluster" ports: - "7000-7007:7000-7007" elasticsearch: image: evalphobia/elasticsearch:2.4.0 ports: - "9200:9200"
  18. Docker まとめ - Docker とは ? - コンテナ型の仮想化環境 - Docker

    Image - 構成がまとまったもの - コンテナを作成するファイルシステムや設定をまとめたもの - Dockerfile を書いて build & run - Docker Hub - Docker 社が運営するリポジトリ - 公式の Docker イメージは Docker Hub で探すとよい
  19. k8s Components (Master Node & Worker Node) - Master Node

    - API Server - k8s cluster のゲートウェイ - etcd - 共有設定の保持とディスカバリに 使う KVS - controller - API Server を利用してクラスタの 状態を監視 & 操作する - Worker Node - docker - Pod を起動する - kubelet - マスターとの通信 - kube-proxy - プロキシ、ロードバランス https://x-team.com/blog/introduction-kubernetes-architecture/
  20. k8s Components (Master Node & Worker Node) - Master Node

    - API Server - k8s cluster のゲートウェイ - etcd - 共有設定の保持とディスカバリに 使う KVS - controller - API Server を利用してクラスタの 状態を監視 & 操作する - Worker Node - docker - Pod を起動する - kubelet - マスターとの通信 - kube-proxy - プロキシ、ロードバランス https://x-team.com/blog/introduction-kubernetes-architecture/ めっちゃムズカシイ
  21. - Service に外部からアクセスするための 仕組み - e.g. - GCE LB -

    ALB - annotation でクラウドスペシフィックな 設定を記載 Ingress
  22. これね // main.go package main import ( "fmt" "net/http" )

    func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, World") } func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8888", nil) }
  23. $ docker image tag my-app gcr.io/sakajunquality-public/my-app:v1 $ docker image push

    gcr.io/sakajunquality-public/my-app:v1 まずはイメージをレジストリに上げる
  24. チュートリアル用に Kubernetes 作成 $ gcloud container clusters create my-cluster ...

    $ gcloud container clusters get-credentials my-cluster --zone asia-northeast1-c ...
  25. // deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: app1 namespace:

    tutorial labels: app: app1 spec: replicas: 3 selector: matchLabels: app: app1 Pod/ReplicaSetを定義 // 続き template: metadata: labels: app: app1 spec: containers: - name: app1 Image: gcr.io/sakajunquality-public/my-app:v1 ports: - containerPort : 8888 protocol: TCP
  26. // deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: app1 namespace:

    tutorial labels: app: app1 spec: replicas: 3 selector: matchLabels: app: app1 apps/v1のDeploymentでまとめて定義 // 続き template: metadata: labels: app: app1 spec: containers: - name: app1 Image: gcr.io/sakajunquality-public/my-app:v1 ports: - containerPort : 8888 protocol: TCP
  27. // deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: app1 namespace:

    tutorial labels: app: app1 spec: replicas: 3 selector: matchLabels: app: app1 push下イメージを指定 // 続き template: metadata: labels: app: app1 spec: containers: - name: app1 image: gcr.io/sakajunquality-public/my-app:v1 ports: - containerPort : 8888 protocol: TCP
  28. // deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: app1 namespace:

    tutorial labels: app: app1 spec: replicas: 3 selector: matchLabels: app: app1 replicaの数も指定 // 続き template: metadata: labels: app: app1 spec: containers: - name: app1 Image: gcr.io/sakajunquality-public/my-app:v1 ports: - containerPort : 8888 protocol: TCP
  29. 次にserviceの作成 // service.yaml apiVersion: v1 kind: Service metadata: name: app1-service

    namespace: tutorial annotations: cloud.google.com/neg : '{"ingress": true}' spec: selector: app: app1 ports: - protocol: TCP port: 8081 targetPort: 8888
  30. selectorでdeploymentのラベルを指定 // service.yaml apiVersion: v1 kind: Service metadata: name: app1-service

    namespace: tutorial annotations: cloud.google.com/neg : '{"ingress": true}' spec: selector: app: app1 ports: - protocol: TCP port: 8081 targetPort: 8888
  31. // ingress.yaml apiVersion: extensions/v1beta1 kind: Ingress metadata: name: my-lb namespace:

    tutorial spec: rules: - http: paths: - path: / backend: serviceName: app1-service servicePort: 8081 ingressでロードバランサーを定義
  32. $ kubectl -n tutorial get ingress NAME HOSTS ADDRESS PORTS

    AGE my-app * aa.bb.xxx.yyy 80 3m ロードバランサーのIPを確認
  33. NO さっきの pod Pod container More practical pod Pod container

    my-app my-app container nginx container logger
  34. AWS Container Services - Registry - ECR - Orchestration -

    ECS - EKS - Compute - Fargate - EC2 - Auto Scaling Group - Code* - CodeDeploy - CodeBuild - CodePipeline https://aws.amazon.com/jp/containers/services/
  35. ECR - ECR = Elastic Container Registry - IAM Policy

    / Role で ECR リソースの利用を細かく制御可能
  36. ECS / EKS - ECS ( Elastic Container Service )

    - 他の AWS サービスとシームレスに連携 - Fargate を使えばサーバの管理が不要 - 起動タイプで EC2 or Fargate を選べる - EKS ( Elastic Container Service for Kubernetes ) - k8s のマネージドサービス - Master のみマネージド - Worker Node は EC2 を立てて運用する - Fargate は未対応 - (AWS のサービスとしては ) ECS より後発
  37. Fargate - Fargate とは - ECS で使えるフルマネージドなコンピューティングエンジン - 課金体系 -

    実際に Task を起動していた vCPU / メモリに対する時間単位の課金 - 最近単価が下がったが、 EC2 との価格比較をすると少し割高ではある
  38. Fargateのメリット - メリット - 管理コストが減る - コンテナのオートスケールに合わせて EC2 もオートスケールさせる手間が ない

    - Worker Node がどの AZ に配置されてるか意識する必要がない - 余剰リソースがなくなる - EC2 を使うときはコンテナ全体が使っているより余剰のリソースを確保する 必要がある
  39. EKSとECSのControl Plane と Data Plane - Control Plane - コンテナを管理する場所

    - どの Node に配置するかなど制御する - AWS のサービスでいうと、 ECS / EKS - Data Plane - コンテナが実行される場所 - Control Plane に各 Node やコンテナの状態をフィードバックする - AWS のサービスでいうと、 Fargate / FC2
  40. Code* (CodeBuild / CodeDeploy / CodePipeline) - CodeBuild - buildspec.yaml

    に ecr push を書いて ECR への push を行う - CodeDeploy - ECS で Blue/Green Deployment のサポート - CodePipeline - ECR 上のイメージをデータソースとして使える - ビルド環境として使うイメージを ECR で管理できるようになった - これまでは CodeBuild で Docker イメージを管理していた - Deploy Target に ECS も対応
  41. Why EKS ? - Why not ECS ? - 前任者が既に

    GKE で一部サービスを稼働させていた - yaml 使いまわしたい - EKS でも途中まで組んだ形跡があった - Why not GKE ? - 既存のアプリケーションが AWS で稼働しておりデータが AWS にある - また、 AWS でしか使えないデータベースを使いたい - e.g. DynamoDB Aurora
  42. VPC / Subnet / Security Group / IAM 設定 ...

    わかるけど、クラスターが立つまでに やることが多いな。。。。
  43. Create k8s cluster by eksctl - Go 製の cli アプリ

    - eksctl create cluster - CloudFormation が裏側で動いてくれる - 一通りよしなにクラスターを作成してくれる - VPC / Subnet - IAM - EKS Cluster - Worker Node (EC2 + ASG) - 検証用途なら最速 - デフォルトだと新規の VPC が作られる - ( 既存の VPC を使うオプションもある ) https://github.com/weaveworks/eksctl
  44. $ eksctl create cluster \ --name test-cluster \ --region ap-northeast-1

    \ --nodes 2 \ --nodes-min 1 \ --nodes-max 2 \ --node-type t2.small スペックをしっかり指定するの巻
  45. Nodeも指定したとおり、 t2.smallが2台できてる $ kubectl get nodes NAME STATUS ROLES AGE

    VERSION ip-192-168-38-92.ap-northeast-1.compute.internal Ready <none> 1s v1.11.5 ip-192-168-74-18.ap-northeast-1.compute.internal Ready <none> 1s v1.11.5 $ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 1s
  46. Clusterの方は EKS Clusterと、 それが乗っかる VPC / Subnet / Route Table

    などなどのネット ワーク周りを作って いる。
  47. ECR Login > $(aws ecr get-login --no-include-email --region ap-northeast-1) Login

    Succeeded ECR への Login コマンドを 取得 & 実行.
  48. Makefile化すると便利 NAME=my-app TAG=1.00 CONTAINER_PORT=8888 LOCAL_PORT=8888 AWS_ACCOUNT_NUMBER=1234567890 login: ## generate `docker

    login` command aws ecr get-login --no-include-email --region ap-northeast-1 build: ## docker build docker build -t $(NAME):$(TAG) . run: ## docker run docker run --rm -it -p $(LOCAL_PORT):$(CONTAINER_PORT) $(NAME):$(TAG) tag: ## tag docker tag $(NAME):$(TAG) $(AWS_ACCOUNT_NUMBER).dkr.ecr.ap-northeast-1.amazonaws.com/$(NAME ):$(TAG) push: ## push 事前にAWSコンソールでリポジトリを作っておき、そこへpushする docker push $(AWS_ACCOUNT_NUMBER).dkr.ecr.ap-northeast-1.amazonaws.com/$(NAME ):$(TAG) release: build tag push そういえば、 一連のコマンドは こんな感じで Makefileにしておくと 取り回しやすい
  49. podが立っている。 $ kubectl get pods NAME READY STATUS RESTARTS AGE

    golang-sample-app-xxxxxxxxxd-dhgj2 1/1 Running 0 1s golang-sample-app-xxxxxxxxxd-q6pls 1/1 Running 0 1s golang-sample-app-xxxxxxxxxd-zlm25 1/1 Running 0 1s
  50. TYPE: LoadBalancerのServiceができた! $ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP

    PORT(S) AGE golang-sample-app LoadBalancer 10.100.17.74 hoge.ap-northeast-1.elb.amazonaws.com 80:30498/TCP 3d kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 4d
  51. EXTERNAL-IP に ELBのDNS名が書かれているぞ $ kubectl get services NAME TYPE CLUSTER-IP

    EXTERNAL-IP PORT(S) AGE golang-sample-app LoadBalancer 10.100.17.74 hoge.ap-northeast-1.elb.amazonaws.com 80:30498/TCP 3d kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 4d
  52. コンソールで確認すると ELB (Classic) が作られている! $ kubectl get services NAME TYPE

    CLUSTER-IP EXTERNAL-IP PORT(S) AGE golang-sample-app LoadBalancer 10.100.17.74 hoge.ap-northeast-1.elb.amazonaws.com 80:30498/TCP 3d kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 4d
  53. これをapplyすると、 ingressができる。 $ kubectl apply -f manifest/ingress.yaml ingress.extensions/golang-sample-app created $

    kubectl get ing NAME HOSTS ADDRESS PORTS AGE golang-sample-app * hoge.ap-northeast-1.elb.amazonaws.com 80 2m
  54. まとめ : eksctlでEKS Clusterたててみた - クラスターをたてる - 検証だけなら eksctl でパッと立てられる

    - CloudFormation でもろもろリソースが用意される - Deployment - Kubernetes なので GKE とおなじ - ALB でインターネットからアクセスさせる - Service:NodePort と Ingress でやる - annotation は AWS 固有の設定を書く
  55. Create k8s cluster by Terraform - Production Ready in eureka

    - 既存の VPC 上でクラスタ構築したい - リソースは terraform で管理されてる - EKS もなるべく terraform で管理したい
  56. Terraform : VPC & Subnet - # VPC resource "aws_vpc"

    "vpc" { cidr_block = "10.xx.yy.zz/ww" instance_tenancy = "default" enable_dns_support = "true" enable_dns_hostnames = "true" } # Subnet ## Public Subnets resource "aws_subnet" "public_1a" { } resource "aws_subnet" "public_1c" { } ## Private Subnets resource "aws_subnet" "private_1a" { } resource "aws_subnet" "private_1c" { } resource "aws_subnet" "private_1b" { } resource "aws_subnet" "private_1d" { } VPC / Subnet は既存のやつ。
  57. Terraform : EKS Cluster resource "aws_eks_cluster" "quality" { name =

    "cluster-quality" role_arn = "${aws_iam_role.quality-cluster.arn}" vpc_config { security_group_ids = ["${aws_security_group.quality-cluster.id}"] subnet_ids = [ "${aws_subnet.private_1b.id}", "${aws_subnet.private_1d.id}", ] } } EKS Cluster は こんなかんじ だぜ ※ IAM Role / SG を別途書く必要あり
  58. Terraform : EC2 Auto Scaling Group data "aws_ami" "eks-worker" {

    filter { name = "name" values = ["eks-worker-*"] } most_recent = true owners = ["xxxxxxxxxxxx"] # Amazon Account ID } resource "aws_launch_configuration" "quality" { associate_public_ip_address = true iam_instance_profile = "${aws_iam_instance_profile.quality-node.name}" image_id = "${data.aws_ami.eks-worker.id}" instance_type = "t2.medium" name_prefix = "terraform-eks-quality" security_groups = ["${aws_security_group.quality-node.id}"] user_data_base64 = "${base64encode(local.quality-node-userdata)}" lifecycle { create_before_destroy = true } } resource "aws_autoscaling_group" "quality" { desired_capacity = 2 launch_configuration = "${aws_launch_configuration.quality.id}" max_size = 2 min_size = 1 name = "terraform-eks-quality" vpc_zone_identifier = [ "${aws_subnet.private_1b.id}", "${aws_subnet.private_1d.id}", ] tag { key = "Name" value = "terraform-eks-quality" propagate_at_launch = true } tag { key = "kubernetes.io/cluster/cluster-quality" value = "owned" propagate_at_launch = true } } ※ Instance Profile / SG などなど別途書く必要あり
  59. まとめ : Terraformでクラスターを立てる - 既存リソースに組み込むかたちで本番に入れたい - やや分量が増えるが、既存のインフラと同じく terraform でできる -

    それなりに手間はかかる ※Terraformで作ったALBなどをClusterに 認識させるには、タグとかで頑張る必要 があるので注意だよ
  60. ECS

  61. ECS - K8S と似てるけどちょっとちがう概念たち - Cluster / Service / Task

    Definition / etc ... https://qiita.com/marnie_ms4/items/202deb8f587233a17cca
  62. ECSの良さ - AWS のサービスとの密な連携 - CodeDeploy で Blue/Green してくれる -

    Task ( ≒ k8s でいう Pod) ごとに異なる IAM Role を付与できる - Fargate を使えるのがでかい - EC2 の面倒を見なくてすむ - スケールするとき Service のスケールだけ考えれば良い - EC2 の場合 - Service のスケール時に EC2 もスケールさせるので手間かかる - 常にバッファ積むので余分にお金かかる
  63. Containers on AWS まとめ - ECR - プライベートなコンテナリポジトリ - EKS

    or ECS - どちらも Control Plane - k8s の生態系に乗りたいか、 AWS の他サービスとなめらかに連携したいか - Fargate - よい
  64. 開発者視点のOverview - ChatOps 経由でのデプロイ - @nana prepare {env} {region]-{service} -

    差分確認 - 前回 deploy との commit 差分 (GitHub) - 確認して deploy ボタンを押す - デプロイ完了 - Slack 通知
  65. SRE視点の既存のOverview - インターフェイスは ChatOps - Slackbot で CodeBuild がキック -

    ビルド完了後、 bot が CodeDeploy を API 経由で呼び出してデプロイ BotApp RTM API Calls
  66. CI/CD まとめ - Kubernetes では GitOps という考えがある - CodePipelines を使うとシンプルなパイプラインも構築できそう

    - あたらしいワークフローを作りつつも、 - 既存の開発者のインターフェイスはできるだけ変えずにやりたい
  67. Monitoring - 監視は大事だよね - Kubernetes そのものの監視 - ノードの監視 - コンテナの監視

    - タグとラベルつかう - リソース使用量 - ヘルスチェック - etc ...
  68. Takeaways - Docker & k8s の基礎 - k8s は覚えること多めだけど -

    焦らず 1 個ずつ確実に理解する - AWS のコンテナ関連サービス - ECR - EKS / ECS - Fargate - CI/CD - GitOps - CodePipeline - What’s Next - 監視 / ロギング などまだまだ考えることある - 今後のロードマップ
  69. Appendix - https://docs.docker.com/ - https://kubernetes.io/docs/home/ - https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/getting-started.html - https://github.com/weaveworks/eksctl -

    https://www.weave.works/blog/gitops-operations-by-pull-request - https://www.slideshare.net/AmazonWebServicesJapan/20180214-aws-black- belt-online-seminar-amazon-container-services