index.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. import * as awsx from '@pulumi/awsx'
  2. import * as aws from '@pulumi/aws'
  3. import * as eks from '@pulumi/eks'
  4. import * as docker from '@pulumi/docker'
  5. import * as k8s from '@pulumi/kubernetes'
  6. import * as pulumi from '@pulumi/pulumi'
  7. import { CaddyServiceDeployment, configMapFromFile } from 'pulumi-common'
  8. import * as fs from 'fs'
  9. const awsConfig = new pulumi.Config('aws')
  10. const config = new pulumi.Config()
  11. const name = 'storage-node'
  12. const wsProviderEndpointURI = config.require('wsProviderEndpointURI')
  13. const queryNodeHost = config.require('queryNodeEndpoint')
  14. const workerId = config.require('workerId')
  15. const accountURI = config.get('accountURI')
  16. const keyFile = config.get('keyFile')
  17. const lbReady = config.get('isLoadBalancerReady') === 'true'
  18. const configColossusImage = config.get('colossusImage') || `joystream/colossus:latest`
  19. const colossusPort = parseInt(config.get('colossusPort') || '3333')
  20. const storage = parseInt(config.get('storage') || '40')
  21. const isMinikube = config.getBoolean('isMinikube')
  22. const additionalVolumes: pulumi.Input<pulumi.Input<k8s.types.input.core.v1.Volume>[]> = []
  23. const additionalVolumeMounts: pulumi.Input<pulumi.Input<k8s.types.input.core.v1.VolumeMount>[]> = []
  24. if (!accountURI && !keyFile) {
  25. throw new Error('Must specify either Key file or Account URI')
  26. }
  27. const additionalParams: string[] | pulumi.Input<string>[] = []
  28. export let kubeconfig: pulumi.Output<any>
  29. export let colossusImage: pulumi.Output<string> = pulumi.interpolate`${configColossusImage}`
  30. let provider: k8s.Provider
  31. if (isMinikube) {
  32. provider = new k8s.Provider('local', {})
  33. } else {
  34. // Create a VPC for our cluster.
  35. const vpc = new awsx.ec2.Vpc('storage-node-vpc', { numberOfAvailabilityZones: 2, numberOfNatGateways: 1 })
  36. // Create an EKS cluster with the default configuration.
  37. const cluster = new eks.Cluster('eksctl-storage-node', {
  38. vpcId: vpc.id,
  39. subnetIds: vpc.publicSubnetIds,
  40. instanceType: 't2.medium',
  41. providerCredentialOpts: {
  42. profileName: awsConfig.get('profile'),
  43. },
  44. })
  45. provider = cluster.provider
  46. // Export the cluster's kubeconfig.
  47. kubeconfig = cluster.kubeconfig
  48. // Create a repository
  49. const repo = new awsx.ecr.Repository('colossus-image')
  50. // Build an image and publish it to our ECR repository.
  51. colossusImage = repo.buildAndPushImage({
  52. context: './docker_dummy',
  53. dockerfile: './docker_dummy/Dockerfile',
  54. args: { SOURCE_IMAGE: colossusImage! },
  55. })
  56. }
  57. const resourceOptions = { provider: provider }
  58. // Create a Kubernetes Namespace
  59. const ns = new k8s.core.v1.Namespace(name, {}, resourceOptions)
  60. // Export the Namespace name
  61. export const namespaceName = ns.metadata.name
  62. const appLabels = { appClass: name }
  63. const pvc = new k8s.core.v1.PersistentVolumeClaim(
  64. `${name}-pvc`,
  65. {
  66. metadata: {
  67. labels: appLabels,
  68. namespace: namespaceName,
  69. name: `${name}-pvc`,
  70. },
  71. spec: {
  72. accessModes: ['ReadWriteOnce'],
  73. resources: {
  74. requests: {
  75. storage: `${storage}Gi`,
  76. },
  77. },
  78. },
  79. },
  80. resourceOptions
  81. )
  82. if (keyFile) {
  83. const keyConfigName = new configMapFromFile(
  84. 'key-config',
  85. {
  86. filePath: keyFile,
  87. namespaceName: namespaceName,
  88. },
  89. resourceOptions
  90. ).configName
  91. const remoteKeyFilePath = '/joystream/key-file.json'
  92. additionalParams.push(`--keyFile=${remoteKeyFilePath}`)
  93. const passphrase = config.get('passphrase')
  94. if (passphrase) {
  95. additionalParams.push(`--password=${passphrase}`)
  96. }
  97. additionalVolumes.push({
  98. name: 'keyfile-volume',
  99. configMap: {
  100. name: keyConfigName,
  101. },
  102. })
  103. additionalVolumeMounts.push({
  104. mountPath: remoteKeyFilePath,
  105. name: 'keyfile-volume',
  106. subPath: 'fileData',
  107. })
  108. }
  109. // Create a Deployment
  110. const deployment = new k8s.apps.v1.Deployment(
  111. name,
  112. {
  113. metadata: {
  114. namespace: namespaceName,
  115. labels: appLabels,
  116. },
  117. spec: {
  118. replicas: 1,
  119. selector: { matchLabels: appLabels },
  120. template: {
  121. metadata: {
  122. labels: appLabels,
  123. },
  124. spec: {
  125. containers: [
  126. {
  127. name: 'colossus',
  128. image: colossusImage,
  129. imagePullPolicy: 'IfNotPresent',
  130. workingDir: '/joystream/storage-node',
  131. env: [
  132. {
  133. name: 'WS_PROVIDER_ENDPOINT_URI',
  134. // example 'wss://18.209.241.63.nip.io/'
  135. value: wsProviderEndpointURI,
  136. },
  137. {
  138. name: 'DEBUG',
  139. value: 'joystream:*',
  140. },
  141. {
  142. name: 'COLOSSUS_PORT',
  143. value: `${colossusPort}`,
  144. },
  145. {
  146. name: 'QUERY_NODE_ENDPOINT',
  147. value: queryNodeHost,
  148. },
  149. {
  150. name: 'WORKER_ID',
  151. value: workerId,
  152. },
  153. // ACCOUNT_URI takes precedence over keyFile
  154. {
  155. name: 'ACCOUNT_URI',
  156. value: accountURI,
  157. },
  158. ],
  159. volumeMounts: [
  160. {
  161. name: 'colossus-data',
  162. mountPath: '/data',
  163. subPath: 'data',
  164. },
  165. {
  166. name: 'colossus-data',
  167. mountPath: '/keystore',
  168. subPath: 'keystore',
  169. },
  170. ...additionalVolumeMounts,
  171. ],
  172. command: ['yarn'],
  173. args: [
  174. 'storage-node',
  175. 'server',
  176. '--worker',
  177. workerId,
  178. '--port',
  179. `${colossusPort}`,
  180. '--uploads=/data',
  181. '--sync',
  182. '--syncInterval=1',
  183. '--queryNodeEndpoint',
  184. queryNodeHost,
  185. '--apiUrl',
  186. wsProviderEndpointURI,
  187. ...additionalParams,
  188. ],
  189. ports: [{ containerPort: colossusPort }],
  190. },
  191. ],
  192. volumes: [
  193. {
  194. name: 'colossus-data',
  195. persistentVolumeClaim: {
  196. claimName: `${name}-pvc`,
  197. },
  198. },
  199. ...additionalVolumes,
  200. ],
  201. },
  202. },
  203. },
  204. },
  205. resourceOptions
  206. )
  207. // Create a LoadBalancer Service for the Deployment
  208. const service = new k8s.core.v1.Service(
  209. name,
  210. {
  211. metadata: {
  212. labels: appLabels,
  213. namespace: namespaceName,
  214. name: 'storage-node',
  215. },
  216. spec: {
  217. type: isMinikube ? 'NodePort' : 'ClusterIP',
  218. ports: [{ name: 'port-1', port: colossusPort }],
  219. selector: appLabels,
  220. },
  221. },
  222. resourceOptions
  223. )
  224. // Export the Service name
  225. export const serviceName = service.metadata.name
  226. // Export the Deployment name
  227. export const deploymentName = deployment.metadata.name
  228. const caddyEndpoints = [
  229. ` {
  230. reverse_proxy storage-node:${colossusPort}
  231. }`,
  232. ]
  233. export let endpoint1: pulumi.Output<string> = pulumi.interpolate``
  234. export let endpoint2: pulumi.Output<string> = pulumi.interpolate``
  235. if (!isMinikube) {
  236. const caddy = new CaddyServiceDeployment(
  237. 'caddy-proxy',
  238. { lbReady, namespaceName: namespaceName, caddyEndpoints },
  239. resourceOptions
  240. )
  241. endpoint1 = pulumi.interpolate`${caddy.primaryEndpoint}`
  242. endpoint2 = pulumi.interpolate`${caddy.secondaryEndpoint}`
  243. }