Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Stretcher
Search
FUJIWARA Shunichiro
March 22, 2016
Technology
7k
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Stretcher
Shibuya.go#2
FUJIWARA Shunichiro
March 22, 2016
More Decks by FUJIWARA Shunichiro
See All by FUJIWARA Shunichiro
多摩川(.dev)ランニング入門 / Tamagawa.dev#3
fujiwara3
3
570
クラウドを使う側から、作る側へ / 大吉祥寺.pm 2026前夜祭
fujiwara3
8
2.2k
作るべきものと向き合う - ecspresso 8年間の開発史から学ぶ技術選定 / 技術選定con findy 2026
fujiwara3
9
6.3k
さくらのクラウドでのシークレット管理を考える/tamachi.sre#2
fujiwara3
2
420
Amazon ECS デプロイツール ecspresso の開発を支える「正しい抽象化」の探求 / YAPC::Fukuoka 2025
fujiwara3
13
14k
パフォーマンスチューニングのために普段からできること/Performance Tuning: Daily Practices
fujiwara3
9
7k
alecthomas/kong はいいぞ
fujiwara3
7
2.6k
ecspressoの設計思想に至る道 / sekkeinight2025
fujiwara3
12
3.8k
さくらのIaaS基盤のモニタリングとOpenTelemetry/OSC Hokkaido 2025
fujiwara3
4
5.7k
Other Decks in Technology
See All in Technology
脆弱性診断って、何をしているの? 〜弱点を見つけて、会社の改善につなげるまで〜
keeg
0
920
事業成長とデータ活用を止めないためのマネジメントへ挑戦
hiracky16
0
390
無理せず続ける、子育てエンジニアの学び
codmoninc
PRO
0
870
SAENM ~Jupyterで残すPoCの試行錯誤、未来の自分と仲間を助ける記録!~
terapyon
0
110
PagerDuty Tech Hub-鳴らすけれど、人は動かない
ezaki
0
170
Running Kotlin + Quarkus with Lambda
nealle
1
3.7k
「Docker Desktop不要の時代」は 来たのか?
ueponx
0
770
アウトプット初心者、半年間のふりかえり
4o1sobblue
0
430
Tofu-Controller lightning talk @ Containerday 2026
gionn
0
110
「事実」を「計測」して、「解釈」する 〜AWS Lambdaのメモリ最適解の見つけ方〜
maimyyym
0
170
初海外で注意すべきこと12選!!!安全快適なre:invent生活にするために
yuuuuuuu168
1
160
今さら理解するウェブフォントver.2026
takanorip
3
770
Featured
See All Featured
Six Lessons from altMBA
skipperchong
29
4.5k
AI in Enterprises - Java and Open Source to the Rescue
ivargrimstad
0
1.5k
B2B Lead Gen: Tactics, Traps & Triumph
marketingsoph
1
280
A Tale of Four Properties
chriscoyier
163
24k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
2.2k
[Rails World 2026] Durable orchestration on Rails: from continuation to workflow
palkan
1
750
Data-driven link building: lessons from a $708K investment (BrightonSEO talk)
szymonslowik
1
1.4k
RailsConf 2023
tenderlove
30
1.6k
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
74
43k
The Curse of the Amulet
leimatthew05
3
16k
Making the Leap to Tech Lead
cromwellryan
135
10k
Effective software design: The role of men in debugging patriarchy in IT @ Voxxed Days AMS
baasie
1
570
Transcript
Stretcher Shibuya.go #2 2016-03-22 @fujiwara
@fujiwara github.com/fujiwara sfujiwara.hatenablog.com ٕज़෦
What's Stretcher github.com/fujiwara/stretcher Consul / Serf ͱ࿈ܞͯ͠ಈ͘σϓϩΠπʔϧ
Inspired by github.com/sorah/mamiya & AWS CodeDeploy
ઃܭํ AWS͡Όͳͯ͘ಈ͘ tar & rsync & ίϚϯυ࣮ߦ Consul / Serf
ͱૄ݁߹ GoͰॻ͘ Ұͭͷ͜ͱΛ͏·͘Δ
Architecture
ಋೖ࣮ ʙ100 ιʔγϟϧήʔϜɺࣗࣾαʔϏεʙ10(λΠτϧ) ʙ100 ΛՃ͢Δ minne ͷٕज़ج൫ઓུ www.slideshare.net/hsbt/minne-54244702
StretcherͷΔ͜ͱ(1) 1. Manifest URLΛඪ४ೖྗ͔ΒಡΉ JSON(Consul Event) ·ͨ text 1ߦ(SerfͳͲ) 2.
ManifestΛऔಘ s3 or http(s) or file scheme 3. Manifest(YAML)ʹैͬͯDeployॲཧ download & extract & rsync & command
Manifest? src: s3://example.com/app.tar.gz checksum: e0840daaa97cd2cf2175f9e5d133ffb3324a2b93 dest: /home/stretcher/app commands: pre: -
/path/to/notify_start post: - /path/to/restart_app success: - /path/to/notify_ok failure: - /path/to/notify_ng excludes: - "*.pid" - "*.socket"
StretcherͷΔ͜ͱ(2) 1. Manifestʹهड़͞Εͨ src(tar)Λऔಘ 2. pre ίϚϯυΛ࣮ߦ 3. tarΛςϯϙϥϦσΟϨΫτϦʹల։ 4.
dest σΟϨΫτϦʹ rsync 5. post ίϚϯυΛ࣮ߦ 6. success or failure ίϚϯυΛ࣮ߦ
StretcherͷΒͳ͍͜ͱ build, tar, S3ͷΞοϓϩʔυ, consul eventൃߦͳͲ֎ github.com/pepabo/capistrano-stretcher Rundeckͱ͏ྫ kikumoto.hatenablog.com/entry/2015/09/16/190036
Imprementation
Imprementation ͓͜ͱΘΓ: εϥΠυͷίʔυΤϥʔॲཧͷলུଟΊͰ͢
نײ ຊମ700ߦɺςετؚΊͯ1200ߦఔͷখ͞͞ 71 cmd/stretcher/main.go 120 aws.go 66 aws_test.go 115 command.go
101 command_test.go 31 consul.go 62 consul_test.go 212 manifest.go 245 manifest_test.go 171 stretcher.go ------------------------------------------------- 720 total 474 total
go-latestͰ࠷৽όʔδϣϯผ $ stretcher -v version: v0.4.1 build: 2016-02-15T18:36:48+0900 0.4.1 is
not latest, you should upgrade to 0.4.2
go-latestͰ࠷৽όʔδϣϯผ github.com/tcnksm/go-latest func checkLatest(version string) { version = fixVersionStr(version) githubTag
:= &latest.GithubTag{ Owner: "fujiwara", Repository: "stretcher", FixVersionStrFunc: fixVersionStr, } res, err := latest.Check(githubTag, version) if err != nil { fmt.Println(err) return } if res.Outdated { fmt.Printf("%s is not latest, you should upgrade to %s\n", version, res.Current) } }
go-latestͰ࠷৽όʔδϣϯผ • GithubTag - GitHub্ͷλάͱൺֱ • JSON - JSON APIͷϨεϙϯεͱൺֱ
{ "version":"1.2.3", "message":"Fix crash bug when XXX" } • HTMLMeta - URLͰఏڙ͞ΕΔHTMLͷϝλλάͱൺֱ <meta name="go-latest" content="stretcher 0.4.2 ...">
versionͷΛbuild࣌ʹຒΊࠐΉ Makefile GIT_VER := $(shell git describe --tags) packages: gox
(ུ) -ldflags "-X main.version ${GIT_VER}" Gitͷtag͕ main.version ͷͱͯ͠ઃఆ͞ΕΔ
math/rand ͷSeedΛ͍͍ײ͡ʹॳظԽ͢Δ ίϚϯυϥΠϯΦϓγϣϯ -random-sleep ͰదʹSleep ىಈλΠϛϯάΛࢄΒ͢ import crand "crypto/rand" import
mrand "math/rand" // http://stackoverflow.com/questions/6181260/generating-random-numbers-in-go var s int64 if err := binary.Read(crand.Reader, binary.LittleEndian, &s); err != nil { s = time.Now().UnixNano() } mrand.Seed(s)
ඪ४ೖྗͷParse • ConsulͰJSON(ྻܗࣜ)͕͞ΕΔ • SerfͰվߦऴςΩετ͕͞ΕΔ ࣗͰશ෦ಡ·ͳ͍Ͱɺ͍͍ײ͡ʹผ͍ͨ͠
ඪ४ೖྗ͔ΒJSON ·ͨ 1ߦΛಡΉ bufio.Reader.Peek() Ͱઌ಄Λ͍ͯಡΈ͚ func parseEvents() (string, error) {
reader := bufio.NewReader(os.Stdin) b, _ := reader.Peek(1) if string(b) == "[" { ev, err := ParseConsulEvents(reader) if err != nil { return "", err } return string(ev.Payload), nil } else { line, _, err := reader.ReadLine() return string(line), err } }
Consul event JSON PayloadʹBase64͞Ε͕ͨೖ͍ͬͯΔ [ { "ID": "1d6731a8-833c-1aff-94e5-aa5e5a77da9f" "Name": "deploy",
"Payload": "czM6Ly9leGFtcGxlLmNvbS9wYXRoL3RvL3Rhci5neg==", "NodeFilter": "", "ServiceFilter": "", "TagFilter": "", "Version": 1, "LTime": 2, }, ... ]
Consul event JSON ΛಡΉ type ConsulEvents []ConsulEvent type ConsulEvent struct
{ ID string `json:"ID"` Name string `json:"Name"` Payload []byte `json:"Payload"` LTime int `json:"LTime"` } []byte Ͱఆ͓ٛͯ͘͠ͱBase64Λdecodeͯ͘͠ΕΔ
S3, HTTP(S), File͔Βಉ͡Α͏ʹಡΉ Manifestͷ src ʹ4छྨͷURL scheme͕༻Մೳ • s3:// •
http:// • https:// • file:// io.ReadCloser interfaceΛѻ͏Α͏ʹઃܭͯ͠நԽ
S3, HTTP(S), File͔Βಉ͡Α͏ʹಡΉ Manifestͷ src ʹ4छྨͷURL scheme͕༻Մೳ • s3:// •
http:// • https:// • file:// io.ReadCloser interfaceΛѻ͏Α͏ʹઃܭͯ͠நԽ
getURL() - S3, HTTP(S), File͔ΒಡΉ stretcher.getURL() io.ReadCloser, error Λฦ͢
func getURL(urlStr string) (io.ReadCloser, error) { u, _ := url.Parse(urlStr) switch u.Scheme { case "s3": return getS3(u) case "http", "https": return getHTTP(u) case "file": return getFile(u) default: return nil, fmt.Errorf("manifest URL scheme must be s3 or http(s) or file: %s", urlStr) } }
getS3() - S3͔Βऔಘ͢Δ github.com/AdRoll/goamz func getS3(u *url.URL) (io.ReadCloser, error) {
// credentialͷಡΈ͜Έ(ུ) client := s3.New(AWSAuth, AWSRegion) bucket := client.Bucket(u.Host) return bucket.GetReader(u.Path) } // goamz/s3 func (b *Bucket) GetReader(path string) (rc io.ReadCloser, err error) {
getHTTP() - HTTP(S)͔Βऔಘ͢Δ func getHTTP(u *url.URL) (io.ReadCloser, error) { req,
_ := http.NewRequest("GET", u.String(), nil) resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } return resp.Body, nil }
getFile() - File͔Βऔಘ͢Δ func getFile(u *url.URL) (io.ReadCloser, error) { file,
err := os.Open(u.Path) if err != nil { return nil, err } return file, nil }
srcΛಡΈࠐΜͰϑΝΠϧʹอଘ อଘ͠ͳ͕Β͍ͭͰʹchecksumܭࢉ func (m *Manifest) Deploy() error { src, _
:= getURL(m.Src) defer src.Close() tmp, _ := ioutil.TempFile(os.TempDir(), "stretcher") defer os.Remove(tmp.Name()) written, sum, err := m.copyAndCalcHash(tmp, src)
ϑΝΠϧΛอଘ͠ͳ͕Βchecksumܭࢉ Goຊମͷ io.CopyBuffer() + ϋογϡܭࢉ func (m *Manifest) copyAndCalcHash(dst io.Writer,
src io.Reader) (int64, string, error) { h, _ := m.newHash() // ͨͱ͑ sha256.New() buf := make([]byte, 32*1024) for { nr, er := src.Read(buf) // bufʹಡΈࠐΜͰ if nr > 0 { h.Write(buf[0:nr]) // ϋογϡΛܭࢉ nw, ew := dst.Write(buf[0:nr]) // dstʹॻ͘ if nw > 0 { written += int64(nw) }
if ew != nil { err = ew break }
if nr != nw { err = io.ErrShortWrite break } } if er == io.EOF { break } if er != nil { err = er break } } s := fmt.Sprintf("%x", h.Sum(nil)) return written, s, err }
औಘΛ͍͍ײ͡ʹϩάʹग़͢ Wrote 218,761,853 bytes to /tmp/stretcher900495202 (in 4.187495 sec, 52MB/s)
github.com/dustin/go-humanize begin := time.Now() // URLऔಘͱϑΝΠϧॻ͖ࠐΈ duration := float64(time.Now().Sub(begin).Nanoseconds()) / 1000000000 log.Printf("Wrote %s bytes to %s (in %s sec, %s/s)", humanize.Comma(written), tmp.Name(), humanize.Ftoa(duration), humanize.Bytes(uint64(float64(written)/duration)), )
tar & rsyncͷ࣮ߦ os/exec.Cmd.CombinedOutput() ֎෦ίϚϯυ࣮ߦɺඪ४ग़ྗɺඪ४Τϥʔग़ྗΛ·ͱΊͯฦ͢ out, err := exec.Command("tar", "xf",
tmp.Name()).CombinedOutput()
ϢʔβఆٛίϚϯυ(pre,post)ͷ࣮ߦ sh -c ʹ࣮ͯ͠ߦ out, err := exec.Command("sh", "-c", c.String()).CombinedOutput()
pipe, && || ͳͲ͕ॻ͚ΔͷͰศར ࣮ࡍͷྫ post: - /sbin/service zabbix-agent status || sleep 30 && sudo /sbin/service zabbix-agent start
ϢʔβఆٛίϚϯυ(success, failure)ͷ࣮ߦ stretcherͷ࣮ߦϩά͕ίϚϯυͷstdinʹ͞ΕΔ༷ package stretcher var LogBuffer bytes.Buffer // άϩʔόϧ
func Init() { log.SetOutput(io.MultiWriter(os.Stderr, &LogBuffer)) ඪ४logϞδϡʔϧͷग़ྗઌΛ os.Stderr ͱ LogBuffer ʹ ඪ४Τϥʔग़ྗ͠ͳ͕ΒίϚϯυʹ͢όοϑΝʹཷΊΔ
ϩάΛstdinʹͯ͠ίϚϯυ࣮ߦ buf := bytes.NewBuffer(src.Bytes()) c.InvokePipe(buf) func (c CommandLine) InvokePipe(src io.Reader)
error { cmd := exec.Command("sh", "-c", c.String()) stdin, _ := cmd.StdinPipe() stdout, _ := cmd.StdoutPipe() stderr, _ := cmd.StderrPipe() cmd.Start()
֎෦ίϚϯυͷstdinϩάΛྲྀ͠ࠐΉ stdinΛಡ·ͳ͍߹EPIPE͕ൃੜ͢ΔͷͰແࢹ var wg sync.WaitGroup wg.Add(3) // src => cmd.stdin
go func() { _, err := io.Copy(stdin, src) if e, ok := err.(*os.PathError); ok && e.Err == syscall.EPIPE { // ignore EPIPE } else if err != nil { log.Println("failed to write to STDIN of", c.String(), ":", err) } stdin.Close() wg.Done() }()
֎෦ίϚϯυͷstdout,stderrΛಡΈࠐΉ sync.WaitGroupͰͪ߹Θͤͳ͍ͱඌ͕ΕΔ(͜ͱ͕͋Δ) // cmd.stdout => stretcher.stdout go func() { io.Copy(os.Stdout,
stdout) stdout.Close() wg.Done() }() // cmd.stderr => stretcher.stderr go func() { io.Copy(os.Stderr, stderr) stderr.Close() wg.Done() }() wg.Wait() return cmd.Wait() }
·ͱΊ interfaceΛ͏·͘͏ͱ͖ͬ͢Γͨ͠ίʔυʹ ඪ४Ϟδϡʔϧๅͷࢁ stretcherʹ͍ͭͯͷ࣭ɺ͝ཁ͓ؾܰʹ @fujiwara ·Ͱ