اتوماسیون زیرساخت با گولنگ - SSH، API و Config Management
2025/11/24یکی از کاربردهای عملی Go برای DevOps، ساخت ابزارهای اتوماسیون زیرساخت است. برخلاف اسکریپتهای Bash یا Python، ابزارهای Go قابل توزیع، سریع و type-safe هستند.
در این آموزش، چندین سناریوی واقعی را پیادهسازی میکنیم.
۱. SSH Automation
اجرای دستور روی سرور
package main
import (
"bytes"
"fmt"
"log"
"os"
"time"
"golang.org/x/crypto/ssh"
)
func connectSSH(host, user, keyPath string) (*ssh.Client, error) {
// خواندن private key
key, err := os.ReadFile(keyPath)
if err != nil {
return nil, fmt.Errorf("cannot read key file: %w", err)
}
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
return nil, fmt.Errorf("cannot parse key: %w", err)
}
config := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // فقط برای تست!
Timeout: 10 * time.Second,
}
client, err := ssh.Dial("tcp", host+":22", config)
if err != nil {
return nil, fmt.Errorf("cannot connect: %w", err)
}
return client, nil
}
func runCommand(client *ssh.Client, command string) (string, error) {
session, err := client.NewSession()
if err != nil {
return "", err
}
defer session.Close()
var stdout, stderr bytes.Buffer
session.Stdout = &stdout
session.Stderr = &stderr
if err := session.Run(command); err != nil {
return "", fmt.Errorf("%s: %w", stderr.String(), err)
}
return stdout.String(), nil
}
func main() {
client, err := connectSSH(
"192.168.1.100",
"ubuntu",
os.Getenv("HOME")+"/.ssh/id_rsa",
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
// اجرای دستور
output, err := runCommand(client, "uptime && df -h")
if err != nil {
log.Fatal(err)
}
fmt.Println(output)
}go get golang.org/x/crypto/sshاجرای دستور روی چندین سرور همزمان
package main
import (
"bytes"
"fmt"
"os"
"sync"
"time"
"golang.org/x/crypto/ssh"
)
type Server struct {
Name string
Host string
User string
KeyPath string
}
type Result struct {
Server string
Output string
Error error
}
func runOnServer(server Server, command string, results chan<- Result, wg *sync.WaitGroup) {
defer wg.Done()
// خواندن key
key, err := os.ReadFile(server.KeyPath)
if err != nil {
results <- Result{Server: server.Name, Error: err}
return
}
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
results <- Result{Server: server.Name, Error: err}
return
}
config := &ssh.ClientConfig{
User: server.User,
Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: 10 * time.Second,
}
client, err := ssh.Dial("tcp", server.Host+":22", config)
if err != nil {
results <- Result{Server: server.Name, Error: err}
return
}
defer client.Close()
session, err := client.NewSession()
if err != nil {
results <- Result{Server: server.Name, Error: err}
return
}
defer session.Close()
var stdout bytes.Buffer
session.Stdout = &stdout
if err := session.Run(command); err != nil {
results <- Result{Server: server.Name, Error: err}
return
}
results <- Result{Server: server.Name, Output: stdout.String()}
}
func main() {
keyPath := os.Getenv("HOME") + "/.ssh/id_rsa"
servers := []Server{
{Name: "web-1", Host: "192.168.1.101", User: "ubuntu", KeyPath: keyPath},
{Name: "web-2", Host: "192.168.1.102", User: "ubuntu", KeyPath: keyPath},
{Name: "db-1", Host: "192.168.1.103", User: "ubuntu", KeyPath: keyPath},
}
command := "hostname && uptime"
results := make(chan Result, len(servers))
var wg sync.WaitGroup
// اجرای همزمان روی همه سرورها
for _, server := range servers {
wg.Add(1)
go runOnServer(server, command, results, &wg)
}
// بستن channel بعد از اتمام همه goroutines
go func() {
wg.Wait()
close(results)
}()
// جمعآوری نتایج
for result := range results {
fmt.Printf("\n=== %s ===\n", result.Server)
if result.Error != nil {
fmt.Printf("Error: %v\n", result.Error)
} else {
fmt.Println(result.Output)
}
}
}کپی فایل با SCP
package main
import (
"fmt"
"io"
"os"
"path/filepath"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
func uploadFile(client *ssh.Client, localPath, remotePath string) error {
// ایجاد SFTP client
sftpClient, err := sftp.NewClient(client)
if err != nil {
return err
}
defer sftpClient.Close()
// باز کردن فایل محلی
localFile, err := os.Open(localPath)
if err != nil {
return err
}
defer localFile.Close()
// ایجاد فایل remote
remoteFile, err := sftpClient.Create(remotePath)
if err != nil {
return err
}
defer remoteFile.Close()
// کپی محتوا
_, err = io.Copy(remoteFile, localFile)
return err
}
func downloadFile(client *ssh.Client, remotePath, localPath string) error {
sftpClient, err := sftp.NewClient(client)
if err != nil {
return err
}
defer sftpClient.Close()
remoteFile, err := sftpClient.Open(remotePath)
if err != nil {
return err
}
defer remoteFile.Close()
localFile, err := os.Create(localPath)
if err != nil {
return err
}
defer localFile.Close()
_, err = io.Copy(localFile, remoteFile)
return err
}
// Deploy یک دایرکتوری کامل
func deployDirectory(client *ssh.Client, localDir, remoteDir string) error {
sftpClient, err := sftp.NewClient(client)
if err != nil {
return err
}
defer sftpClient.Close()
return filepath.Walk(localDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// مسیر نسبی
relPath, _ := filepath.Rel(localDir, path)
remotePath := filepath.Join(remoteDir, relPath)
if info.IsDir() {
return sftpClient.MkdirAll(remotePath)
}
// کپی فایل
localFile, err := os.Open(path)
if err != nil {
return err
}
defer localFile.Close()
remoteFile, err := sftpClient.Create(remotePath)
if err != nil {
return err
}
defer remoteFile.Close()
_, err = io.Copy(remoteFile, localFile)
fmt.Printf("Uploaded: %s\n", remotePath)
return err
})
}go get github.com/pkg/sftp۲. کار با API های Cloud
AWS SDK - لیست EC2 Instances
package main
import (
"context"
"fmt"
"log"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/ec2"
)
func main() {
ctx := context.Background()
// بارگذاری config از environment یا ~/.aws/credentials
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("us-east-1"))
if err != nil {
log.Fatal(err)
}
client := ec2.NewFromConfig(cfg)
// لیست instances
output, err := client.DescribeInstances(ctx, &ec2.DescribeInstancesInput{})
if err != nil {
log.Fatal(err)
}
fmt.Println("EC2 Instances:")
fmt.Println("─────────────────────────────────────────")
for _, reservation := range output.Reservations {
for _, instance := range reservation.Instances {
name := ""
for _, tag := range instance.Tags {
if *tag.Key == "Name" {
name = *tag.Value
break
}
}
fmt.Printf("ID: %s\n", *instance.InstanceId)
fmt.Printf("Name: %s\n", name)
fmt.Printf("Type: %s\n", instance.InstanceType)
fmt.Printf("State: %s\n", instance.State.Name)
if instance.PublicIpAddress != nil {
fmt.Printf("Public IP: %s\n", *instance.PublicIpAddress)
}
fmt.Println("─────────────────────────────────────────")
}
}
}go get github.com/aws/aws-sdk-go-v2/config
go get github.com/aws/aws-sdk-go-v2/service/ec2HTTP Client برای REST API
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
type APIClient struct {
BaseURL string
HTTPClient *http.Client
Token string
}
func NewAPIClient(baseURL, token string) *APIClient {
return &APIClient{
BaseURL: baseURL,
Token: token,
HTTPClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}
func (c *APIClient) doRequest(ctx context.Context, method, path string, body any) (*http.Response, error) {
var bodyReader io.Reader
if body != nil {
jsonBody, err := json.Marshal(body)
if err != nil {
return nil, err
}
bodyReader = bytes.NewBuffer(jsonBody)
}
req, err := http.NewRequestWithContext(ctx, method, c.BaseURL+path, bodyReader)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+c.Token)
return c.HTTPClient.Do(req)
}
// مثال: Cloudflare API
type DNSRecord struct {
ID string `json:"id,omitempty"`
Type string `json:"type"`
Name string `json:"name"`
Content string `json:"content"`
TTL int `json:"ttl"`
}
type CloudflareResponse struct {
Success bool `json:"success"`
Result []DNSRecord `json:"result"`
}
func (c *APIClient) ListDNSRecords(ctx context.Context, zoneID string) ([]DNSRecord, error) {
resp, err := c.doRequest(ctx, "GET", "/zones/"+zoneID+"/dns_records", nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result CloudflareResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return result.Result, nil
}
func (c *APIClient) CreateDNSRecord(ctx context.Context, zoneID string, record DNSRecord) error {
resp, err := c.doRequest(ctx, "POST", "/zones/"+zoneID+"/dns_records", record)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("API error: %s", string(body))
}
return nil
}
func main() {
client := NewAPIClient(
"https://api.cloudflare.com/client/v4",
"your-api-token",
)
ctx := context.Background()
records, err := client.ListDNSRecords(ctx, "zone-id")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
for _, record := range records {
fmt.Printf("%s %s -> %s\n", record.Type, record.Name, record.Content)
}
}۳. پردازش فایلهای Config
خواندن و نوشتن YAML
package main
import (
"fmt"
"log"
"os"
"gopkg.in/yaml.v3"
)
type ServerConfig struct {
Name string `yaml:"name"`
Host string `yaml:"host"`
Port int `yaml:"port"`
Environment string `yaml:"environment"`
Features []string `yaml:"features"`
Database DatabaseConfig `yaml:"database"`
Labels map[string]string `yaml:"labels"`
}
type DatabaseConfig struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
Name string `yaml:"name"`
Username string `yaml:"username"`
Password string `yaml:"password,omitempty"`
}
type Config struct {
Version string `yaml:"version"`
Servers []ServerConfig `yaml:"servers"`
}
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var config Config
if err := yaml.Unmarshal(data, &config); err != nil {
return nil, err
}
return &config, nil
}
func SaveConfig(path string, config *Config) error {
data, err := yaml.Marshal(config)
if err != nil {
return err
}
return os.WriteFile(path, data, 0644)
}
func main() {
// ایجاد config جدید
config := &Config{
Version: "1.0",
Servers: []ServerConfig{
{
Name: "api-server",
Host: "api.example.com",
Port: 8080,
Environment: "production",
Features: []string{"auth", "logging", "metrics"},
Database: DatabaseConfig{
Host: "db.example.com",
Port: 5432,
Name: "myapp",
Username: "admin",
},
Labels: map[string]string{
"team": "backend",
"version": "v2",
},
},
},
}
// ذخیره
if err := SaveConfig("config.yaml", config); err != nil {
log.Fatal(err)
}
fmt.Println("Config saved to config.yaml")
// خواندن
loaded, err := LoadConfig("config.yaml")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Loaded %d servers\n", len(loaded.Servers))
for _, server := range loaded.Servers {
fmt.Printf("- %s (%s:%d)\n", server.Name, server.Host, server.Port)
}
}go get gopkg.in/yaml.v3جایگزینی Template در Config
package main
import (
"bytes"
"fmt"
"os"
"text/template"
)
type DeploymentVars struct {
AppName string
Environment string
Replicas int
Image string
Tag string
Port int
Resources ResourceVars
}
type ResourceVars struct {
CPURequest string
CPULimit string
MemoryRequest string
MemoryLimit string
}
const deploymentTemplate = `apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .AppName }}
namespace: {{ .Environment }}
labels:
app: {{ .AppName }}
env: {{ .Environment }}
spec:
replicas: {{ .Replicas }}
selector:
matchLabels:
app: {{ .AppName }}
template:
metadata:
labels:
app: {{ .AppName }}
spec:
containers:
- name: {{ .AppName }}
image: {{ .Image }}:{{ .Tag }}
ports:
- containerPort: {{ .Port }}
resources:
requests:
cpu: {{ .Resources.CPURequest }}
memory: {{ .Resources.MemoryRequest }}
limits:
cpu: {{ .Resources.CPULimit }}
memory: {{ .Resources.MemoryLimit }}
`
func GenerateManifest(vars DeploymentVars) (string, error) {
tmpl, err := template.New("deployment").Parse(deploymentTemplate)
if err != nil {
return "", err
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, vars); err != nil {
return "", err
}
return buf.String(), nil
}
func main() {
vars := DeploymentVars{
AppName: "api-server",
Environment: "production",
Replicas: 3,
Image: "registry.example.com/api",
Tag: "v1.2.3",
Port: 8080,
Resources: ResourceVars{
CPURequest: "100m",
CPULimit: "500m",
MemoryRequest: "128Mi",
MemoryLimit: "512Mi",
},
}
manifest, err := GenerateManifest(vars)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Println(manifest)
// ذخیره در فایل
os.WriteFile("deployment.yaml", []byte(manifest), 0644)
}۴. Health Check Tool
package main
import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"os"
"sync"
"time"
)
type Check struct {
Name string `json:"name"`
Type string `json:"type"` // http, tcp, dns
Target string `json:"target"`
Expected int `json:"expected,omitempty"` // for HTTP
Timeout string `json:"timeout"`
}
type CheckResult struct {
Name string `json:"name"`
Status string `json:"status"` // ok, fail
Duration time.Duration `json:"duration"`
Error string `json:"error,omitempty"`
}
type Config struct {
Checks []Check `json:"checks"`
}
func checkHTTP(ctx context.Context, target string, expected int, timeout time.Duration) error {
client := &http.Client{Timeout: timeout}
req, err := http.NewRequestWithContext(ctx, "GET", target, nil)
if err != nil {
return err
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if expected > 0 && resp.StatusCode != expected {
return fmt.Errorf("expected %d, got %d", expected, resp.StatusCode)
}
return nil
}
func checkTCP(target string, timeout time.Duration) error {
conn, err := net.DialTimeout("tcp", target, timeout)
if err != nil {
return err
}
conn.Close()
return nil
}
func checkDNS(target string, timeout time.Duration) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
resolver := &net.Resolver{}
_, err := resolver.LookupHost(ctx, target)
return err
}
func runCheck(ctx context.Context, check Check) CheckResult {
start := time.Now()
timeout, _ := time.ParseDuration(check.Timeout)
if timeout == 0 {
timeout = 10 * time.Second
}
var err error
switch check.Type {
case "http":
err = checkHTTP(ctx, check.Target, check.Expected, timeout)
case "tcp":
err = checkTCP(check.Target, timeout)
case "dns":
err = checkDNS(check.Target, timeout)
default:
err = fmt.Errorf("unknown check type: %s", check.Type)
}
result := CheckResult{
Name: check.Name,
Duration: time.Since(start),
}
if err != nil {
result.Status = "fail"
result.Error = err.Error()
} else {
result.Status = "ok"
}
return result
}
func main() {
config := Config{
Checks: []Check{
{Name: "Google", Type: "http", Target: "https://google.com", Expected: 200, Timeout: "5s"},
{Name: "DNS Google", Type: "dns", Target: "google.com", Timeout: "5s"},
{Name: "Redis", Type: "tcp", Target: "localhost:6379", Timeout: "3s"},
{Name: "API", Type: "http", Target: "http://localhost:8080/health", Expected: 200, Timeout: "5s"},
},
}
ctx := context.Background()
results := make([]CheckResult, len(config.Checks))
var wg sync.WaitGroup
for i, check := range config.Checks {
wg.Add(1)
go func(idx int, c Check) {
defer wg.Done()
results[idx] = runCheck(ctx, c)
}(i, check)
}
wg.Wait()
// نمایش نتایج
fmt.Println("Health Check Results")
fmt.Println("═══════════════════════════════════════")
allOK := true
for _, r := range results {
status := "✓"
if r.Status == "fail" {
status = "✗"
allOK = false
}
fmt.Printf("%s %s (%v)", status, r.Name, r.Duration.Round(time.Millisecond))
if r.Error != "" {
fmt.Printf(" - %s", r.Error)
}
fmt.Println()
}
// خروجی JSON
output, _ := json.MarshalIndent(results, "", " ")
os.WriteFile("health-results.json", output, 0644)
if !allOK {
os.Exit(1)
}
}۵. Log Parser
package main
import (
"bufio"
"encoding/json"
"fmt"
"os"
"regexp"
"sort"
"strings"
)
type LogEntry struct {
IP string
Timestamp string
Method string
Path string
Status string
Size string
}
type Stats struct {
TotalRequests int
ByStatus map[string]int
ByPath map[string]int
ByIP map[string]int
}
// Apache/Nginx combined log format
var logPattern = regexp.MustCompile(
`^(\S+) \S+ \S+ \[([^\]]+)\] "(\S+) (\S+) [^"]*" (\d+) (\d+|-)`,
)
func parseLogLine(line string) (*LogEntry, error) {
matches := logPattern.FindStringSubmatch(line)
if matches == nil {
return nil, fmt.Errorf("invalid log format")
}
return &LogEntry{
IP: matches[1],
Timestamp: matches[2],
Method: matches[3],
Path: matches[4],
Status: matches[5],
Size: matches[6],
}, nil
}
func analyzeLog(filePath string) (*Stats, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer file.Close()
stats := &Stats{
ByStatus: make(map[string]int),
ByPath: make(map[string]int),
ByIP: make(map[string]int),
}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
entry, err := parseLogLine(scanner.Text())
if err != nil {
continue
}
stats.TotalRequests++
stats.ByStatus[entry.Status]++
stats.ByPath[entry.Path]++
stats.ByIP[entry.IP]++
}
return stats, scanner.Err()
}
func topN(m map[string]int, n int) []struct {
Key string
Count int
} {
type kv struct {
Key string
Count int
}
var sorted []kv
for k, v := range m {
sorted = append(sorted, kv{k, v})
}
sort.Slice(sorted, func(i, j int) bool {
return sorted[i].Count > sorted[j].Count
})
if len(sorted) > n {
sorted = sorted[:n]
}
var result []struct {
Key string
Count int
}
for _, item := range sorted {
result = append(result, struct {
Key string
Count int
}{item.Key, item.Count})
}
return result
}
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: logparser <logfile>")
os.Exit(1)
}
stats, err := analyzeLog(os.Args[1])
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
fmt.Printf("Total Requests: %d\n\n", stats.TotalRequests)
fmt.Println("Status Codes:")
for status, count := range stats.ByStatus {
pct := float64(count) / float64(stats.TotalRequests) * 100
fmt.Printf(" %s: %d (%.1f%%)\n", status, count, pct)
}
fmt.Println("\nTop 10 Paths:")
for _, item := range topN(stats.ByPath, 10) {
fmt.Printf(" %s: %d\n", item.Key, item.Count)
}
fmt.Println("\nTop 10 IPs:")
for _, item := range topN(stats.ByIP, 10) {
fmt.Printf(" %s: %d\n", item.Key, item.Count)
}
// خروجی JSON
output, _ := json.MarshalIndent(stats, "", " ")
os.WriteFile("log-stats.json", output, 0644)
}جمعبندی
در این آموزش یاد گرفتید:
| موضوع | کتابخانه |
|---|---|
| SSH Automation | golang.org/x/crypto/ssh |
| SFTP File Transfer | github.com/pkg/sftp |
| AWS SDK | github.com/aws/aws-sdk-go-v2 |
| YAML Config | gopkg.in/yaml.v3 |
| Template Processing | text/template (built-in) |
این ابزارها به شما کمک میکنند:
- اسکریپتهای Bash پیچیده را به Go تبدیل کنید
- ابزارهای قابل توزیع بسازید
- اتوماسیون type-safe داشته باشید