調低EBS 的磁碟大小

前言

最近收到AWS的個人帳單以後, 突然發現我帳單上的金額與我的想像有些落差, 也因為這樣子, 開始來對一下是哪些項目在吃錢…

大概對了一下, 發現主要的花費在EC2 instance 與 EBS 兩個項目上面, EC2 instance 這邊的花費就是題外話了, 但EBS這邊就慢讓我意外的… 細看了以後才發現當初不知道為什麼把使用的EBS 成 使用30GB, 但我實際上的使用也才6GB左右…
(雖然這也不是什麼大錢, 但每個月給它這樣扣也是覺得還不如把錢省下來去訂閱其他有用服務….)

第一次實驗

原本想說把root volume EBS 調低應該不是件難事, 結果沒想到AWS預設上是不支援這件是的, 它可以往上調高, 但不支援調低…

在網路上找了不少相關的教學文, 主要的作法都是, 大概都是…

  • 先建立一個較小的EBS volume
  • 再把這個新的 EBS volume 掛到我們的EC2上面以後, 做好格式化以及 rsync 的資料拷貝
  • 最後設定好 /boot 以後, 就可以把新的EBS volume來取代原有的 EBS root volume.
    (題外話, 要把EBS volume 設定為 EC2 上面的 /dev/sda1 才會成為那台EC2上的root volume)

第一次實驗結果

經由上述步驟的教學文實際嘗試以後, 都會卡再做出來的EBS volume是無法當成是原本這一台EC2的root volume, 它會直接讓那台EC2開不起來. (機器顯示 running, 但是ssh 連不到…)

上述的測試其實我參考了不少文章, 但最後卡的點都是一樣的, 做出來的 EBS volume是不能用的… (我這邊使用的EC2 t4g family, 不太確定跟ARM 版本的 grub-install 有關??? 也許今天使用的是x86的機器就可以? 之後有機會再來試試)

另一種Workaround的方式

因為上面測試也花了不少時間, 所以最後就自己做了一些流程上的調整如下:

  • 建立一台新的EC2, 並設定root volume為我想要的大小 (6GB), 並且OS的版本也是用與就機器一樣的 ubuntu 20.04
  • 接著, 將舊機器的EBS 掛到新的EC2機器上.
  • 透過 rsync 將舊的EC2 資料都複製到新的EC2上. (基本上除了 /boot, /tmp, /mnt, /dev以外, 都要sync 到新的EC2的對應資料夾)
  • 如果先前有設定route53, elastic IP 等等相關的設定, 最後都指定到這台新的EC2機器上.

最後靠這個方式成功把 EBS size降下來了, 不過靠這個方式比較像是duplicate出一台新的EC2, 而不是clone出一模一樣的機器…

Reference:

AWS Lambda的”冷啟動”時間

在Hacker News 上看到的一些相關比較,作者比較了現有AWS Lambda 的程式語言,並記錄每種語言在不同情境下的cold start的時間。

測試架構大概如下:

clients -> HTTP API -> Lambda function -> Dynamo DB

直接跳到測試結果

Cold start:

* All languages(except Java and .Net) have a pretty small cold start.
* Java even cannot start with 128Mb. It needs more memory. But GraalVM can help in this case.
* Rust beats all runtimes for all setups for cold start, the only exception is 128 MB where Python is the best.

AWS Lambda battle 2021: performance comparison for all languages (cold and warm start) | by Aleksandr Filichkin | Sep, 2021 | Medium

Warm start:

* Golang and Rust are the winners. They have the same brilliant performance.
* .Net has almost the same performance as Golang and Rust, but only after 1k iterations(after JIT).
* GraalVM has a stable great performance almost the same as .Net and a bit worse than Rust and Golang. But it doesn’t perform well for the smallest setup.
* Java is the next after GraalVM.The same as .Net, Java needs some time(1–3k iterations) for JIT(C1). Unfortunately for this particular use case, I was not able to achieve the expected great performance after JIT C2 compilation. Maybe AWS just disabled it.
* Python has stable good performance but works too slow for the 128 MB.
* Ruby has almost the same performance as Python, but we see some duration growing after 20 min invocations(after 15k iteration).
* NodeJs is the slowest runtime, after some time it becomes better(JIT?) but still is not good enough. In addition, we see the NodeJS has the worst maximum duration.

AWS Lambda battle 2021: performance comparison for all languages (cold and warm start) | by Aleksandr Filichkin | Sep, 2021 | Medium

Go與Rust是其中最穩定的且效能最好的,在幾乎所有的情境下,都是前2名。(唯一的例外是,Python在128MB RAM 的環境下是最快的)。

對我而言比較意外的是Node.js,沒想到會是最慢的!?

Reference:

AWS Lambda battle 2021: performance comparison for all languages (cold and warm start) | by Aleksandr Filichkin | Sep, 2021 | Medium

AWS egress 的費用

某天剛好看到了這篇文章AWS’s Egregious Egress (cloudflare.com),才知道原來AWS在頻寬這邊的計價(outgoing)真的蠻貴的,尤其是當你所用的AWS region是在一些已開發國家。

https://blog.cloudflare.com/aws-egregious-egress/
https://blog.cloudflare.com/aws-egregious-egress/

這邊算下來的數字來看,同樣的頻寬,如果是使用一般跟ISP租用每個月XX Mbps來相比,在北美這邊可以貴到約ISP 價錢的那邊的80陪。

hmmm…. 真的是蠻貴的…

雖然說AWS 的ingress是不計算費用的,但蠻多資料相關服務,其本身的egress 費用看來是無法避免的,所以這篇文章可以拿來參考一下一些AWS在營運上,頻寬費用上應該要注意的事項…

AWS 複製bucket 的資料到另一個bucket

常使用AWS S3的開發者來說,這複製資訊到另一個bucket 應該算是一個蠻常見的需求,而目前看到AWS Go SDK有支援對應的APICopyObject

在試著使用了以後,發現Go SDK的程式碼中 CopySource欄位的描述不是很清楚,所以不知道要怎麼把值代入這個欄位,直到後來發現AWS 有份文件中有寫到相關的範例:

原來一個正確的CopyObjectInput 會是如下所示:

source := bucket + "/" + item
input := &s3.CopyObjectInput{
  CopySource: aws.String(url.PathEscape(source)), 
  Bucket: aws.String(other),
  Key: aws.String(item)
}

跟我原本預期的有些差距,原本以為合法的CopySource應該會是如像:s3://bucket/item.XD

Reference:

使用AWS Localstack S3 筆記

對於常開發與使用AWS的開發者來說,要實作一些整合測試時通常會比較麻煩點,尤其是通常會需要在有網路的環境下做開發與測試。

Github上的這個Localstack 專案,就是為了解決這個痛點而存在的,它幫助AWS開發者搭建本地端的AWS 服務,舉例來說,我們可以透過Localstack在本地端搭建一個S3讓我們所開發的應用程式去連接。

目前Localstack支援的AWS 服務可以在他們的github頁面上找到,常見的API Gateway, DynamoDB, Lambda and S3都可以使用,如果是使用他們的付費方案, 則有支援更多的AWS 服務。

搭建Localstack S3

目前剛好有用到S3,所以記錄一下透過下面docker-compose.yaml來啟用Localstack的方式:

version: "3.3"
 services:
   localstack:
     image: localstack/localstack
     network_mode: bridge
     ports:
       - "4566:4566"
       - "4571:4571"
     environment:
       - SERVICES=s3
       - EDGE_PORT=4566
       - AWS_DEFAULT_REGION=us-east-1
     volumes:
       - "${TMPDIR:-/tmp/localstack}:/tmp/localstack"
       - "/var/run/docker.sock:/var/run/docker.sock"

NOTE:

  • 這邊的SERVICES是可以指定多個服務的,例如SERVICE=s3,lambda
  • EDGE_PORT目前預設也是4566,主要是用來在本地端服務的port number。
  • 更多詳細的docker-compose.yaml 範例可以參考官方的這個例子

使用AWS CLI 連接 Localstack S3

在透過上面提到的方式搭建好本地端的S3以後,其實就可以直接用AWS CLI來去連到我們剛架好的S3了;唯一要注意的是,我們在使用cli 時必須指定對應的endpoint-url到我們剛架好的本地端url。

透過docker aws cli 連接Localstack s3

docker exec -t -e AWS_ACCESS_KEY_ID=1 -e AWS_SECRET_ACCESS_KEY=abc container_localstack_1 aws --endpoint-url=http://127.0.0.1:4566 s3 mb s3://my_bucket

NOTE:
透過AWS Cli連接 Localstack時,必須指定key_idkey,要不然會報錯

透過AWS Go SDK

如果要透過Go AWS SDK 去連接Localstack S3的話,會有個額外要設定的AWS.Config欄位,其具體的範例程式碼為:

awsCfg := &aws.Config{
   Credentials: credentials.NewStaticCredentials(awsKeyId, awsKeySecret, ""),
   Region:      aws.String(region),
}

if customEndpoint != "" {
   awsCfg.Endpoint = aws.String("http://localhost:4566")
   awsCfg.S3ForcePathStyle = aws.Bool(true)
}

在這邊,設定 S3ForcePathStyle=true 主要是為了讓Go SDK與連接S3的時候會把bucket 的資訊透過path的方式去代入到resource的uri中。

舉例來說,如果我們有個bucket=my_bucket,而我們在這個bucket裡面的一個測試檔案test.txt的完整uri為是http://localhost:4566/my_bucket/test.txt;有了這樣的設定,才會讓我們使用Go AWS SDK時,可以正確的存取Localstack S3上的檔案。

如果我們使用S3ForcePathStyle=false的話,則同個檔案, Go AWS SDK會使用這個uri: http://my_bucket.localhost:4566/test.txt 去存取Localstack上的檔案;顯然地,這個uri是有問題的,因為Localstack S3 是在listen http://localhost:4566,而不是http://my_bucket.localhost:4566這個子網域。

更多詳細的說明可以參考官方的這個issue:https://github.com/aws/aws-sdk-go/issues/2743

Reference:

Python command line

最近為了想上手Python,所以想說用Python寫個簡易的cli 工具當作目標來當練習;這次就來記錄怎麼一步一步的寫出這個cli程式。
程式的需求,是希望可以設定一個定期備份wordpress 內容的服務,並且在備份完了以後,可以上傳到指定的AWS s3.

Requirements

  • 提供flag 可以讓使用者指定要備份的資料夾
  • 提供flag 可以讓使用者指定要上傳的s3 路徑
  • 備份的資料夾必須先經過壓縮後,才會上傳到s3

Dependencies

  • Python 3+
  • Poetry
  • Pip

Poetry 是目前主流的Python套件管理工具,而這次這個repo主要就是使用poetry來管理其相依的library。

Implementation

Cli flags

Config flags 的部分,主要是透過Click這個套件來實作。這個套件在使用上相當容易上手,透過decorator的方式就可以指定程式在執行時,需要帶入哪些指令。

像是下面這段程式碼,就指定了程式在執行時,需要帶入-s--s3_path 作為s3 的路徑;即使我現在還不是很熟decorator,也可以輕易的讓程式支援flag。

@click.command()
@click.option(
     "-s",
     "--s3_path",
     default="",
     envvar="S3_PATH",
     help="The s3 path for uploading. (e.g. s3://path/to/upload )",
     required=True
 )

Entrypoint

除了讓程式支援flag,我們這邊也需要設定程式在執行時的進入點,通常會是一個自定義的function,其參數會是我們先前所設定的flags。

以下面的程式碼為例,我們指定了cli() 作為我們的這個程式的進入點,並且程式必須有2個參數,分別是inputs3_path。(參數的名稱必須與@click.option中的一致)

def cli(input, s3_path):
     if s3_path.find("s3://") != 0:
         logging.error("incorrect s3 path format")
         exit(1)

if name == "main":
    cli()

Config Poetry cli entrypoint

透過先前的設定,我們已經可以透過一般python 的執行方式(python cli.py)來執行我們的程式了,但為了讓我們的cli程式在執行時可以更像一個執行檔,我們可以透過Poetry來包裝我們的程式,這樣就不需要透過pyhton xxx.py的方式執行了!

Poetry entrypoint function

首先,我們必須先變動我們的程式路徑,並且指定一個可以當進入點的function,這邊我們就可以設定一個新的main(),而其function body就是執行我們先前的cli()

def main():
     cli()

接下來,我們必須在project 根資料夾下,新增一個子資料夾,並且把我們的cli.py移至這個子資料夾下,新的路徑結構為下

backup_to_s3_repo/
  poetry.lock
  pyproject.toml
  README.md
  backup_to_s3/
    __init__.py
    cli.py

Poetry entrypoint configuration

在這邊還需要在設定pyproject.toml,主要是為了讓poetry在執行時,可以用”alias”的方式來執行我們的程式,相關的pyproject.toml的內容如下:

下面的設定,可以讓我們執行poetry run backup_to_s3時,實際上去執行backup_to_s3/cli.py中的main()

[tool.poetry.scripts]
 backup_to_s3 = "backup_to_s3.cli:main"

Build and install

在完成前面所提到的設定之後,我們已經可以透過python backup_to_s3/cli.pypoetry run backup_to_s3來執行我們的程式了;但為了讓我們的程式可以更像一般的系統預設的執行程,我們可以透過下面列出的方式來實作。

Build the poetry project

透過poetry build,poetry 會幫我們把整個poetry project與相關的套件封裝成一個tar.gz 檔,並儲存於dist/資料夾下。

在整個封裝的過程中,poetry會幫我們建立對應的.whlsetup.py檔,以便於我們發佈或分享這個tar.gz 檔。

Install poetry build file by pip

在有了上述的tar.gz檔以後,我們就可以透過pip install的方式,將整個backup_to_s3程式安裝成像系統內建的指令程式了,詳細的指令可以參考下面

pip install dist/backup_to_s3-0.1.0.tar.gz

最後,完整的source code 可以參考這裡:
https://github.com/bamoo456/backup-to-s3

Reference

EBS 定價

最近主管告知了上個月的AWS帳單,EC2的帳單暴漲到了$2000!
主管提到了,這是他預期內的金額,不過提醒我要記得關閉最近測試開的那些機器與資源。當然,這金額對我來說是個非常意外的數字,之前的測試都是測試完以後我就馬上關掉機器了,每次測試時間最長也不超過3小時!

後來主管一路追查下去,原來我之前開了一堆io2的EBS,在測試完了以後也沒有刪除,而這些EBS應該就是造成帳單暴漲的原因!!!

這次的失誤,讓我知道以後要記得去看一下EBS是否有忘了刪掉的disk;不過也趁這機會好好研究一下EBS的計價方式:

EBS Volume Charges

AWS的這篇文章中提到的例子,可以看出只要EBS被provision以後,其provision的容量(GB)與IOPS都會被納入計價:

舉個例子,如果我在一台EC2上掛了一個100GB, 10000 IOPS的io2 EBS,則即使我只使用EBS上其中1GB做資料儲存,我一個月需要在EBS上付的金額為:

10000 IOPS * 0.065 USD + 100GB * 0.125 USD = 662.5 $USD

真的認真算下來,才知道io2的EBS計價那麼貴,這次真的是花錢好好的上了一課0rz

Reference

測試Cassandra

最近公司的某個專案可能會用到Cassandra,所以在真的用之前還是先來做個簡易的Benchmark,來看看它是否真的能符合我們的需求(主要是write heavey的情境)。

測試#1

測試情境

  • cluster nodes: AWS EC2 i3.large * 3
  • benchmark node: AWS EC2 c5.2xlarge.

測試 Query如下:

INSERT INTO status (id1 id2, id3, succeed) VALUES ($id1, $id2, $id3, false)

測試程式會不停的隨機產生上述Query,並寫進Cassandra cluster中,並觀察throughput如何:

測試結果

結果蠻不如預期的,目前在concurrent connection = 500的情況下,也只達到9xx左右的qps

Next

目前初步檢視了目前的cluster 設定,發現先前的測試EC2 node主要還是使用EBS gp2,其IOPS感覺只能到100 IOPS左右,再參考了這篇AWS的文章後,感覺可以照著這樣的設定再來測試一次看看。

測試#2

測試情境

沿續上次的測試,但重新設定Cluster 使用EC2 i3.large * 3,但這次則是mount了 NVME SSD (local instance store) 到Cassandra data folder ,並使用了一樣的測試程式與Query。

測試結果

一樣不如預期,qps 也還是只能打到9xx 左右,不過這次有觀察到Cassandra node的cpu 在測試期間都滿載。

Next

從上次的測試來看,cpu cores 必須要加大了,感覺目前是卡在cpu這邊。

測試#3

測試情境

沿續上次的測試,但重新設定Cluster 使用EC2 c5.4xlarge * 3,但這次則是mount了 NVME SSD(EBS io2) 到Cassandra data folder ,並使用了一樣的測試程式與Query。

CREATE INDEX succeed ON status (succeed);

測試結果

若沒有index 的情況下,insert qps 目前可以到 11xxx 左右;若加了index,則insert qps 目前可以到8xxx左右。

測試#4

測試情境

沿續上次的測試,但重新設定Cluster 使用EC2 c5.4xlarge * 8,但這次則是mount了 NVME SSD(EBS io2, iops=5000) 到Cassandra data folder ,並使用了一樣的測試程式與Query與測試#3中的index。

測試結果

加了index,則insert qps 目前可以到14xxx左右,這次觀察到cluster cpu 大概只有跑到50%左右,看來benchmark client 必須要升級了。

測試#5

測試情境

沿續上次的測試,這次升級了Benchmark client 使用c5a.8xlarge,測試cluster的部分則是使用前面測試所留下來的EC2 c5a.4xlarge * 8.

測試結果

這次觀察到即使在使用concurrent connection = 2000的情況下,cluster cpu 大概只有跑到70%左右,而整體的qps約在16xxx;

從這邊來看感覺又是卡在IOPS=5000的部分,目前每個cluster node 可以設定的EBS 為io2, IOPS=5000,IOPS設太高的話會在一開始無法同時開8個EC2 c5a.4xlarge

測試#6

測試情境

沿續上次的測試,這次試著再加了3 台 c5a.4xlarge到Cassandra cluster中,即是最後的cluster node 數量為

c5a.4xlarge * 10

測試結果

這次觀察在同一台機器,並且開啟2個benchmark 程式並使用concurrent connection = 2000的情況下,cluster cpu 大概只有跑到70%左右,而整體的qps約在20xxx;