DockerにDocker in docker, aws-cli, kubectlをインストール

目的

  • Docker in docker実現したい
  • AWS-cli入れたい
  • kubectl入れたい

以上を1コンテナで。

結論

FROM docker:stable-dind

WORKDIR /k8s

# aws cliの実行が失敗するので、glibcのverupしてインストール
# https://github.com/aws/aws-cli/issues/4685#issuecomment-615872019
ENV GLIBC_VER=2.31-r0

# install glibc compatibility for alpine
RUN apk --no-cache add \
        binutils \
        curl \
    && curl -sL https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub -o /etc/apk/keys/sgerrand.rsa.pub \
    && curl -sLO https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_VER}/glibc-${GLIBC_VER}.apk \
    && curl -sLO https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_VER}/glibc-bin-${GLIBC_VER}.apk \
    && apk add --no-cache \
        glibc-${GLIBC_VER}.apk \
        glibc-bin-${GLIBC_VER}.apk \
    && curl -sL https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip -o awscliv2.zip \
    && unzip awscliv2.zip \
    && aws/install \
    && rm -rf \
        awscliv2.zip \
        aws \
        /usr/local/aws-cli/v2/*/dist/aws_completer \
        /usr/local/aws-cli/v2/*/dist/awscli/data/ac.index \
        /usr/local/aws-cli/v2/*/dist/awscli/examples \
    && apk --no-cache del \
        binutils \
        curl \
    && rm glibc-${GLIBC_VER}.apk \
    && rm glibc-bin-${GLIBC_VER}.apk \
    && rm -rf /var/cache/apk/*

# install kubectl
RUN apk --no-cache add \
  curl && \
  curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && \
  chmod +x ./kubectl && \
  mv ./kubectl /usr/local/bin/kubectl 

DinDの実行

  • 一度dockerコンテナをバックグラウンドで実行してからじゃないとDinDはうまく動かないとか。
$ docker image build -t dind .
$ docker container run --rm -it --privileged -d --name dindcontainer dind
$ docker exec -it dindcontainer sh
# コンテナ内で
$ docker info

Docker in docker

FROM docker:stable-dind

AWS-cli

  • 現状不具合があるのか、普通にコンテナ上にインストールするだけではうまくいかない様子。

github.com

ENV GLIBC_VER=2.31-r0

# install glibc compatibility for alpine
RUN apk --no-cache add \
        binutils \
        curl \
    && curl -sL https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub -o /etc/apk/keys/sgerrand.rsa.pub \
    && curl -sLO https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_VER}/glibc-${GLIBC_VER}.apk \
    && curl -sLO https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_VER}/glibc-bin-${GLIBC_VER}.apk \
    && apk add --no-cache \
        glibc-${GLIBC_VER}.apk \
        glibc-bin-${GLIBC_VER}.apk \
    && curl -sL https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip -o awscliv2.zip \
    && unzip awscliv2.zip \
    && aws/install \
    && rm -rf \
        awscliv2.zip \
        aws \
        /usr/local/aws-cli/v2/*/dist/aws_completer \
        /usr/local/aws-cli/v2/*/dist/awscli/data/ac.index \
        /usr/local/aws-cli/v2/*/dist/awscli/examples \
    && apk --no-cache del \
        binutils \
        curl \
    && rm glibc-${GLIBC_VER}.apk \
    && rm glibc-bin-${GLIBC_VER}.apk \
    && rm -rf /var/cache/apk/*

kubectl

RUN apk --no-cache add \
  curl && \
  curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && \
  chmod +x ./kubectl && \
  mv ./kubectl /usr/local/bin/kubectl 

メモ[10 things you (probably) don't know about Go]

メモ

  • 10 things you (probably) don't know about Go このメモがとても参考になったので忘れないようにメモ

1. Anonymous structs

var config struct {
    APIKey      string
    OAuthConfig oauth.Config
}

config.APIKey = "BADC0C0A"

7. Method expressions

talks.golang.org

type T struct {}
func (T) Foo(s string) { println(s) }

var fn func(T, string) = T.Foo

Goのhttpリクエストのmockについて

Goでhttpリクエス

いくつか方法はあるようだが、

client := &http.Client{
    CheckRedirect: redirectPolicyFunc,
}

resp, err := client.Get("http://example.com")
// ...

req, err := http.NewRequest("GET", "http://example.com", nil)
// ...
req.Header.Add("If-None-Match", `W/"wyzzy"`)
resp, err := client.Do(req)
// ...

こっちの書き方でクライアントを実装する(http.Clientを使用する)

実装コード

github.com

httpmockを使わない例

  • 以下のように実装できる(説明のためにちょっと修正)
type HttpClient interface {
    Do(req *http.Request) (*http.Response, error)
}

func NewContentUsecase(c HttpClient) *ContentUsecase {
    return &ContentUsecase{
        httpClient: c,
    }
}

type ContentUsecase struct {
    httpClient HttpClient
}

func (content ContentUsecase) GetContent() (string, error) {
    request, _ := http.NewRequest("GET", "https://google.com", nil)
    resp, _ := content.httpClient.Do(request)
    defer resp.Body.Close()
    byteArray, _ := ioutil.ReadAll(resp.Body)

    return string(byteArray), nil
}
  • キモとなるのはGetContentの引数に渡されてくるContentUsecase
  • このContentUsecaseHttpClientインターフェースを満たしており、それを引数で注入する
  • テスト時にはこの引数に渡すものをmockすれば良い

テスト(httpmockを使わない例)

type clientMock struct {
    HttpClient
    MockedDo func(req *http.Request) (*http.Response, error)
}

func (c *clientMock) Do(req *http.Request) (*http.Response, error) {
    return c.MockedDo(req)
}

func TestGetContent(t *testing.T) {
    t.Run("GetContent正常系", func(t *testing.T) {
        spyMocked := &clientMock{
            MockedDo: func(req *http.Request) (*http.Response, error) {
                return &http.Response{
                    StatusCode: http.StatusOK,
                    Body:       ioutil.NopCloser(bytes.NewBufferString("OK")),
                }, nil
            },
        }
        contentUsecase := NewContentUsecase(spyMocked)
        content, err := contentUsecase.GetContent()

        if err != nil {
            t.Errorf("err should be nil: %v", err)
        }

        if content != "OK" {
            t.Errorf("Returned Content invalid: %v", content)
        }
    })
}
  • HttpClientインターフェースの中身をMockする(Do関数を置き換える)
  • 実際のテストコードの中でDo関数が何を返すのかを実装してやれば良い(MockedDoを実装する)

httpmockを使う例

  • こっちのほうはとても簡単
  • でも、標準ライブラリのみで使える前者のほうがいいんではないかとか思ったりする

  • 一部プログラム変更してあります(エラーハンドリングなど)

func GetContentUseMock() (string, error) {
    request, _ := http.NewRequest("GET", "https://example.com", nil)
    client := &http.Client{}
    resp, _ := client.Do(request)
    defer resp.Body.Close()
    byteArray, _ := ioutil.ReadAll(resp.Body)

    return string(byteArray), nil
}

テスト

func TestGetContentUseMock(t *testing.T) {
    t.Run("GetContentUseMock正常系", func(t *testing.T) {
        httpmock.Activate()
        defer httpmock.DeactivateAndReset()

        httpmock.RegisterResponder("GET", "https://example.com",
            func(req *http.Request) (*http.Response, error) {
                res := httpmock.NewStringResponse(200, "ok")
                return res, nil
            },
        )

        content, err := GetContentUseMock()
        if err != nil {
            t.Errorf("error should be nil: %v", err)
        }

        if content != "ok" {
            t.Errorf("content invalid: %v", content)
        }
    })
}
  • RegisterResponderに何を返すのかを書くだけ。

poetryメモ

poertry

pythonでのパッケージ管理の定番のようです(npmのような)

参考

org-technology.com

poetry init

  • プロジェクトの初期化を行う
poetry init

パッケージ追加

poetry add numpy

これを実施することにより、pyproject.tomlが更新されます。 numpy = "^1.18.2"
こんな感じで追記

poetry add pandas librosa

このように複数書くこともできます

パッケージのインストール(tomlの内容をインストールする)

poetry install

pythonプログラムの実行

poetry run python app.py

terraformにて、module間で値(variable,outputs)を渡す

0.12のフォーマット

値の受け渡し

terraformにて、variableを使って変数を表現するが、
moduleをまたいでの受け渡しをどうするか

フォルダ構成の例

├── main.tf
├── modules
│   ├── ec2
│   │   ├── ec2.tf
│   │   └── variables.tf
│   └── network
│       ├── network.tf
│       ├── outputs.tf
│       └── variables.tf

こんな風になっているとして
networkディレクトリとec2ディレクトリでデータを渡したいということ
vpcで作ったvpcのidをec2に渡したいというのが目的

渡し方

modules/network/network.tfにてvpcを定義

# aws_vpc
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true
}

modules/network/outputs.tfにてoutputsを定義(ファイルは分ける必要はないけど、ベストプラクティスとしてよくファイルは別に書かれている)

output "vpc_main" {
  value = aws_vpc.main
}

このようにvpcのresourceを返すことができる


これを、main.tfで受け渡す

module "network" {
  source = "./modules/network"
}

module "ec2" {
  source = "./modules/ec2"

  vpc_main       = module.network.vpc_main
}

vpc_main = module.network.vpc_mainこれがキモで、module.<module名>.<output名>で取得可能


そしてこれをec2モジュールで受ける
modules/ec2/variables.tf

# vpc information.
variable "vpc_main" {}

modules/ec2/ec2.tf

# aws_instance
resource "aws_instance" "app" {
  ami                    = "ami id"
  instance_type          = "t2.micro"
  subnet_id              = var.subnet_for_ec2.id
}

これで値が渡せる

Terraformで、.envのように変数を注入する

terraformにて、任意の変数を外部から注入する

terraform.tfvarsという仕組みを使う

assume_role = "arn:aws:iam::012345678901:role/AssumeRoleForTerraform"
pub_key_value = "ssh key value"

といったように、任意の値を書いておく

そして、実行時にこのファイルを指定する

terraform plan -var-file ./terraform.tfvars 
terraform apply -var-file ./terraform.tfvars 

これで、一部の値を隠すことが可能

IAMのAssumeRoleについて

AssumeRole

権限を移譲するための仕組みのようです。

参考

christina04.hatenablog.com

qiita.com

概要

今回は、アカウントAのAWSリソースを、アカウントBが使うというケースをAssumeRoleでやります

例として、以下のアカウントIDだとします。
- アカウントA
210987654321
- アカウントB
123456789012

つまり、アカウントAが権限を付与する方で、アカウントBが権限を要求する立場です。

権限を付与するアカウント(210987654321)での作業

ロールを作るだけ

AWSのコンソールからIAMを選択
ロールを作成します

そのロール作成の際に、「別のAWSアカウント」を選びます
f:id:y-ni-shi:20200320204154p:plain

次に権限の画面になりますが、任意の権限を選びます
今回アドミン権限にしますが、本来は用途に絞った権限を選択すべきです
f:id:y-ni-shi:20200320204330p:plain

ロールの名前を決めて保存します。
今回はAssumeRoleSampleとしました f:id:y-ni-shi:20200320204430p:plain

権限を以来するアカウント(123456789012)での作業

任意のユーザーを作ります
権限はデフォルトの状態でOKです

任意のグループを作ります。
権限はとりあえずデフォルトの状態でOKです

今回はこの作成したグループにAssumeRoleの権限を付与します

ポリシーの作成

アカウントAに対して権限を移譲してほしいと依頼するわけですが、
その「移譲を依頼する」という権限をつけないといけないぽいです

ポリシーの作成から

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "arn:aws:iam::123456789012:role/AssumeRoleSample"
        }
    ]
}

"arn:aws:iam::123456789012:role/AssumeRoleSample"これがキモです。
アカウントAのIDと、ロール名称を指定します。

そしてこのポリシーを保存。
(AssumeRoleSampleという名前でポリシーを保存.名前は任意です)

そしてこのポリシーをグループにアタッチ
f:id:y-ni-shi:20200320205102p:plain

これで準備完了。

terraform

terraformで実験

terraform {
  required_version = "0.12.24"
}

variable "assume_role" { 
}

provider "aws" {
  region     = "ap-northeast-1"
  assume_role {
    role_arn     = var.assume_role
  }
}

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support = true
  tags = {
    Name = "vpc for test"
  }
}

resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.main.id
  tags = {
    Name = "igw for test"
  }
}

さらに、環境変数を注入して実行します
terraform.tfvars

assume_role = "arn:aws:iam::123456789012:role/AssumeRoleSample"

アクセスキーとIDの環境変数への登録(上記tsvarsでやってもいいけど)

export AWS_ACCESS_KEY_ID=access id
export AWS_SECRET_ACCESS_KEY=access key

このアクセスIDとKEYはアカウントBの情報です。
アカウントBとしてログインしてアカウントAに移譲を依頼。

terraform plan -var-file="terraform.tfvars"

問題なければ

terraform apply -var-file="terraform.tfvars"

これで実行するとAWSリソースができているはず。
アカウントAのコンソールにログインしてVPCができていることを確認。

お片付け

terraform destroy -var-file="terraform.tfvars"