脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - Go语言实现ssh&scp的方法详解

Go语言实现ssh&scp的方法详解

2022-11-25 12:05漫漫Coding路 Golang

这篇文章主要为大家详细介绍了如何利用Go语言实现ssh&scp,文中的示例代码讲解详细,具有一定的参考价值,感兴趣的小伙伴可以了解一下

前言

最近遇到一个临时需求,需要将客户环境中一个服务每天的日志进行一系列复杂处理,并生成数据报表。由于数据处理逻辑复杂,且需要存入数据库,在客户环境使用 shell 脚本无法处理,因此就需要将日志先拷贝到本地,再进行处理;同时为了避免每天人工拷贝日志,需要实现自动化,整条链路自动执行,无需人工干预。平时使用 Go 语言较多,由此就引出了 Go 语言 ssh 连接远程客户服务器,并利用 scp 将数据拷贝下来的一系列操作。

说明:本文中的示例,均是基于Go1.17 64位机器

连接远程服务器并执行命令(ssh)

如下给出了使用 用户名+密码 的方式连接远程服务器并执行了 /usr/bin/whoami 命令的示例,步骤如下:

  • 生成 ClientConfig:想要连接远程服务器,必须要至少指定一种实现了 Auth 的 AuthMethod,我们这里使用密码的方式;同时需要提供 一种用于安全校验远程服务端key的方法 HostKeyCallback,我们这里使用的是不校验的方式 ssh.InsecureIgnoreHostKey(),生产情况下建议使用 ssh.FixedHostKey(key PublicKey)
  • 调用 Dial :Dial 方法与远程服务器建立连接,并返回一个 client ;
  • NewSessionNewSession方法开启一个会话,在一个会话内可以通过 Run 方法执行一个命令。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import (
 "bytes"
 "fmt"
 "log"
  
  "golang.org/x/crypto/ssh"
)
 
func main() {
 
 var (
  username = "your username"
  password = "your password"
  addr     = "ip:22"
 )
 
 config := &ssh.ClientConfig{
  User: username,
  Auth: []ssh.AuthMethod{
   ssh.Password(password),
  },
  HostKeyCallback: ssh.InsecureIgnoreHostKey(),
 }
 client, err := ssh.Dial("tcp", addr, config)
 if err != nil {
  log.Fatal("Failed to dial: ", err)
 }
 defer client.Close()
 
 // 开启一个session,用于执行一个命令
 session, err := client.NewSession()
 if err != nil {
  log.Fatal("Failed to create session: ", err)
 }
 defer session.Close()
 
 // 执行命令,并将执行的结果写到 b 中
 var b bytes.Buffer
 session.Stdout = &b
  
  // 也可以使用 session.CombinedOutput() 整合输出
 if err := session.Run("/usr/bin/whoami"); err != nil {
  log.Fatal("Failed to run: " + err.Error())
 }
 fmt.Println(b.String())  // root
}

上面的例子,我们在 Run 方法里面传入了一个命令,然后远程服务器会将执行结果返回给我们,如果是复杂操作,通过传入命令的方式就比较麻烦。比如上面提到的需求,需要我从 k8s 容器中拷贝出服务每天的日志,拆解后的步骤为:获取服务的多个 k8s pod 名称,根据当前日期,从多个容器中分别拷贝日志文件,然后整合成一个日志文件。针对复杂操作,我们可以在远程服务器编写一个脚本,然后 Run 方法中传入执行脚本的命令。

简单举个示例,我们在远程服务器编写了一个脚本 test.sh,放在了 /opt 目录下,脚本内容 与 调用方式分别如下:

?
1
2
3
4
5
# 脚本文件
#!/bin/bash
today=$(date +"%Y-%m-%d")
# 将数据写入文件
$(df -h > $today.log)
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package main
 
import (
 "fmt"
 "log"
  
  "golang.org/x/crypto/ssh"
)
 
func main() {
 
 var (
  username = "your username"
  password = "your password"
  addr     = "ip:22"
 )
 
 config := &ssh.ClientConfig{
  User: username,
  Auth: []ssh.AuthMethod{
   ssh.Password(password),
  },
  HostKeyCallback: ssh.InsecureIgnoreHostKey(),
 }
 client, err := ssh.Dial("tcp", addr, config)
 if err != nil {
  log.Fatal("Failed to dial: ", err)
 }
 defer client.Close()
 
 
 session, err := client.NewSession()
 if err != nil {
  log.Fatal("Failed to create session: ", err)
 }
 defer session.Close()
 
  // 调用远程服务器脚本脚本
 res, err := session.CombinedOutput("sh /opt/test.sh")
 if err != nil {
  log.Fatal("Failed to run: " + err.Error())
 }
 fmt.Println(string(res))
  
  /*
  Filesystem      Size  Used Avail Use% Mounted on
  devtmpfs        909M     0  909M   0% /dev
  tmpfs           919M   24K  919M   1% /dev/shm
  tmpfs           919M  540K  919M   1% /run
  tmpfs           919M     0  919M   0% /sys/fs/cgroup
  /dev/vda1        50G  6.9G   40G  15% /
  tmpfs           184M     0  184M   0% /run/user/0
  */
}

拷贝远程服务器文件到本地(scp)

拷贝文件步骤比较简单:

  • 建立 ssh client
  • 基于 ssh client 创建 sftp client
  • 打开远程服务器文件并拷贝到本地
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package main
 
import (
 "io"
 "log"
 "os"
 "time"
  
  "github.com/pkg/sftp"
 "golang.org/x/crypto/ssh"
)
 
func main() {
 
 var (
  username = "your username"
  password = "your password"
  addr     = "ip:22"
 )
 
 // 1. 建立 ssh client
 config := &ssh.ClientConfig{
  User: username,
  Auth: []ssh.AuthMethod{
   ssh.Password(password),
  },
  HostKeyCallback: ssh.InsecureIgnoreHostKey(),
 }
 client, err := ssh.Dial("tcp", addr, config)
 if err != nil {
  log.Fatal("Failed to dial: ", err)
 }
 defer client.Close()
 
 // 2. 基于ssh client, 创建 sftp 客户端
 sftpClient, err := sftp.NewClient(client)
 if err != nil {
  log.Fatal("Failed to init sftp client: ", err)
 }
 defer sftpClient.Close()
 
 // 3. 打开远程服务器文件
 filename := time.Now().Format("2006-01-02") + ".log"
 source, err := sftpClient.Open("/opt/" + filename)
 if err != nil {
  log.Fatal("Failed to open remote file: ", err)
 }
 defer source.Close()
 
 // 4. 创建本地文件
 target, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
 if err != nil {
  log.Fatal("Failed to open local file: ", err)
 }
 defer target.Close()
 
 // 5. 数据复制
 n, err := io.Copy(target, source)
 if err != nil {
  log.Fatal("Failed to copy file: ", err)
 }
 log.Println("Succeed to copy file: ", n)
 
}

在 sftp client 中,还有许多方法,例如 WalkReadDirStatMkdir等,针对文件也有 ReadWriteWriteToReadFrom等方法,像操作本地文件系统一样,非常便利。

简单封装下

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package main
 
import (
 "fmt"
 "io"
 "log"
 "os"
 "time"
 
 "github.com/pkg/sftp"
 "golang.org/x/crypto/ssh"
)
 
type Cli struct {
 user   string
 pwd    string
 addr   string
 client *ssh.Client
}
 
 
func NewCli(user, pwd, addr string) Cli {
 return Cli{
  user: user,
  pwd:  pwd,
  addr: addr,
 }
}
 
// Connect 连接远程服务器
func (c *Cli) Connect() error {
 config := &ssh.ClientConfig{
  User: c.user,
  Auth: []ssh.AuthMethod{
   ssh.Password(c.pwd),
  },
  HostKeyCallback: ssh.InsecureIgnoreHostKey(),
 }
 client, err := ssh.Dial("tcp", c.addr, config)
 if nil != err {
  return fmt.Errorf("connect server error: %w", err)
 }
 c.client = client
 return nil
}
 
// Run 运行命令
func (c Cli) Run(shell string) (stringerror) {
 if c.client == nil {
  if err := c.Connect(); err != nil {
   return "", err
  }
 }
 
 session, err := c.client.NewSession()
 if err != nil {
  return "", fmt.Errorf("create new session error: %w", err)
 }
 defer session.Close()
 
 buf, err := session.CombinedOutput(shell)
 return string(buf), err
}
 
// Scp 复制文件
func (c Cli) Scp(srcFileName, targetFileName string) (int64error) {
 if c.client == nil {
  if err := c.Connect(); err != nil {
   return 0, err
  }
 }
 
 sftpClient, err := sftp.NewClient(c.client)
 if err != nil {
  return 0, fmt.Errorf("new sftp client error: %w", err)
 }
 defer sftpClient.Close()
 
 source, err := sftpClient.Open(srcFileName)
 if err != nil {
  return 0, fmt.Errorf("sftp client open file error: %w", err)
 }
 defer source.Close()
 
 target, err := os.OpenFile(targetFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
 if err != nil {
  return 0, fmt.Errorf("open local file error: %w", err)
 }
 defer target.Close()
 
 n, err := io.Copy(target, source)
 if err != nil {
  return 0, fmt.Errorf("copy file error: %w", err)
 }
 return n, nil
}
 
 
// 调用测试
func main() {
 var (
  username = "your username"
  password = "your password"
  addr     = "ip:22"
 )
 
 // 初始化
 client := NewCli(username, password, addr)
 
 // ssh 并运行脚本
 _, err := client.Run("sh /opt/test.sh")
 if err != nil {
  log.Printf("failed to run shell,err=[%v]\n", err)
  return
 }
 
 // scp 文件到本地
 filename := time.Now().Format("2006-01-02") + ".log"
 n, err := client.Scp("/opt/"+filename, filename)
 if err != nil {
  log.Printf("failed to scp file,err=[%v]\n", err)
  return
 }
 log.Printf("Succeed to scp file, size=[%d]\n", n)
 
 // 处理文件并删除本地文件......
}

通过上面的一系列操作,就可以实现了我的需求:

1.编写程序:

  • 连接客户服务器
  • 执行远程服务器的脚本,生成日志文件
  • 拷贝远程服务器的日志文件到本地
  • 处理日志文件
  • 删除本地文件

2.在服务器上启动一个定时任务运行该程序

以上就是Go语言实现ssh&scp的方法详解的详细内容,更多关于Go语言实现ssh scp的资料请关注服务器之家其它相关文章!

原文链接:https://mp.weixin.qq.com/s/nVAAMAW0jIeSMx9oJysUjw

延伸 · 阅读

精彩推荐
  • Golanggoland 设置project gopath的操作

    goland 设置project gopath的操作

    这篇文章主要介绍了goland 设置project gopath的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    管sc10962021-06-21
  • Golanggolang 结构体初始化时赋值格式介绍

    golang 结构体初始化时赋值格式介绍

    这篇文章主要介绍了golang 结构体初始化时赋值格式介绍,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    robin91213712021-03-10
  • GolangGolang泛型的使用方法详解

    Golang泛型的使用方法详解

    这篇文章主要介绍了Golang中泛型的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编...

    doublewe5162022-10-23
  • Golang详解 Go 语言中 Map 类型和 Slice 类型的传递

    详解 Go 语言中 Map 类型和 Slice 类型的传递

    这篇文章主要介绍了详解 Go 语言中 Map 类型和 Slice 类型的传递的相关资料,需要的朋友可以参考下 ...

    mrr4232020-05-08
  • Golang详解Golang互斥锁内部实现

    详解Golang互斥锁内部实现

    本篇文章主要介绍了详解Golang互斥锁内部实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 ...

    诺唯5092020-05-07
  • Golanggo使用consul实现服务发现及配置共享实现详解

    go使用consul实现服务发现及配置共享实现详解

    这篇文章主要为大家介绍了go使用consul实现服务发现及配置共享实现详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职...

    dz456939852022-10-09
  • GolangGo Redis客户端使用的两种对比

    Go Redis客户端使用的两种对比

    这篇文章主要为大家介绍了Go Redis客户端使用对比详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪...

    Johns11682022-07-27
  • Golanggolang 切片的三种使用方式及区别的说明

    golang 切片的三种使用方式及区别的说明

    这篇文章主要介绍了golang 切片的三种使用方式及区别的说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    whatday10172021-05-31